mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-04 20:38:24 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
322 lines
7.5 KiB
C#
322 lines
7.5 KiB
C#
using System.Buffers;
|
|
|
|
namespace Sandbox;
|
|
|
|
[Expose]
|
|
public struct CharacterControllerHelper
|
|
{
|
|
//
|
|
// Inputs and Outputs
|
|
//
|
|
public Vector3 Position;
|
|
public Vector3 Velocity;
|
|
|
|
//
|
|
// Config
|
|
//
|
|
public float Bounce;
|
|
public float MaxStandableAngle;
|
|
public SceneTrace Trace;
|
|
|
|
public CharacterControllerHelper( SceneTrace trace, Vector3 position, Vector3 velocity ) : this()
|
|
{
|
|
Velocity = velocity;
|
|
Position = position;
|
|
Bounce = 0.0f;
|
|
MaxStandableAngle = 10.0f;
|
|
Trace = trace;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Trace this from one position to another
|
|
/// </summary>
|
|
public SceneTraceResult TraceFromTo( Vector3 start, Vector3 end )
|
|
{
|
|
return Trace.FromTo( start, end ).Run();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Try to move to the position. Will return the fraction of the desired velocity that we traveled.
|
|
/// Position and Velocity will be what we recommend using.
|
|
/// </summary>
|
|
public float TryMove( float timestep )
|
|
{
|
|
var timeLeft = timestep;
|
|
float travelFraction = 0;
|
|
|
|
using var moveplanes = new VelocityClipPlanes( Velocity, 3 );
|
|
|
|
for ( int bump = 0; bump < moveplanes.Max; bump++ )
|
|
{
|
|
if ( Velocity.Length.AlmostEqual( 0.0f ) )
|
|
break;
|
|
|
|
var pm = TraceFromTo( Position, Position + Velocity * timeLeft );
|
|
|
|
travelFraction += pm.Fraction;
|
|
timeLeft -= timeLeft * pm.Fraction;
|
|
|
|
if ( !pm.Hit )
|
|
{
|
|
Position = pm.EndPosition;
|
|
break;
|
|
}
|
|
|
|
if ( pm.Fraction > 0 )
|
|
{
|
|
Position = pm.EndPosition;// + pm.Normal * 0.001f;
|
|
moveplanes.StartBump( Velocity );
|
|
}
|
|
|
|
bool standable = pm.Normal.Angle( Vector3.Up ) <= MaxStandableAngle;
|
|
|
|
//Gizmo.Transform = Transform.Zero;
|
|
|
|
//var dot = Velocity.Normal.Dot( pm.Normal );
|
|
//Gizmo.Draw.Color = Color.White;
|
|
//Gizmo.Draw.Text( $"{bump}\n{pm.Normal}\n{dot}", new Transform( pm.StartPosition ) );
|
|
//Gizmo.Draw.Line( pm.StartPosition + Vector3.Up * 5, pm.EndPosition + Vector3.Up * 5 );
|
|
|
|
//Gizmo.Draw.Color = Color.Green;
|
|
//Gizmo.Draw.Line( pm.StartPosition + Vector3.Up * 5, pm.StartPosition + Vector3.Up * 5 + pm.Normal * 3 );
|
|
|
|
if ( !moveplanes.TryAdd( pm.Normal, ref Velocity, standable ? 0 : Bounce ) )
|
|
break;
|
|
}
|
|
|
|
return travelFraction;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move our position by this delta using trace. If we hit something we'll stop,
|
|
/// we won't slide across it nicely like TryMove does.
|
|
/// </summary>
|
|
public SceneTraceResult TraceMove( Vector3 delta )
|
|
{
|
|
var tr = TraceFromTo( Position, Position + delta );
|
|
Position = tr.EndPosition;
|
|
return tr;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Like TryMove but will also try to step up if it hits a wall
|
|
/// </summary>
|
|
public float TryMoveWithStep( float timeDelta, float stepsize )
|
|
{
|
|
var startPosition = Position;
|
|
|
|
// Make a copy of us to stepMove
|
|
var stepMove = this;
|
|
|
|
// Do a regular move
|
|
var fraction = TryMove( timeDelta );
|
|
|
|
// If it got almost all the way then that's cool, use it
|
|
if ( fraction <= 0.01f )
|
|
return fraction;
|
|
|
|
// Move up (as much as we can)
|
|
stepMove.TraceMove( Vector3.Up * stepsize );
|
|
|
|
// if the move delta is too low, we probably won't get up a step
|
|
Vector3 moveBack = 0;
|
|
var moveDelta = stepMove.Velocity.WithZ( 0 ) * timeDelta;
|
|
var deltaLen = moveDelta.Length;
|
|
|
|
// if it's really low, then we're probably moving straight up or down
|
|
// so lets just early out now
|
|
if ( deltaLen < 0.001f )
|
|
return fraction;
|
|
|
|
if ( deltaLen < 0.5f )
|
|
{
|
|
var newDelta = moveDelta.Normal * 0.5f;
|
|
moveBack = moveDelta - newDelta;
|
|
moveDelta = newDelta;
|
|
}
|
|
|
|
// Move across (using existing velocity)
|
|
var stepFraction = stepMove.TraceMove( moveDelta );
|
|
|
|
// Move back down
|
|
var tr = stepMove.TraceMove( Vector3.Down * stepsize );
|
|
|
|
// if we didn't land on something, return
|
|
if ( !tr.Hit ) return fraction;
|
|
|
|
// If we landed on a wall then this is no good
|
|
if ( tr.Normal.Angle( Vector3.Up ) > MaxStandableAngle )
|
|
return fraction;
|
|
|
|
// if the original non stepped attempt moved further use that
|
|
if ( startPosition.Distance( Position.WithZ( startPosition.z ) ) > startPosition.Distance( stepMove.Position.WithZ( startPosition.z ) ) )
|
|
return fraction;
|
|
|
|
if ( !moveBack.IsNearZeroLength )
|
|
{
|
|
stepMove.TraceMove( moveBack );
|
|
}
|
|
|
|
// step move moved further, copy its data to us
|
|
Position = stepMove.Position;
|
|
Velocity = stepMove.Velocity;
|
|
|
|
return stepFraction.Fraction;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Used to store a list of planes that an object is going to hit, and then
|
|
/// remove velocity from them so the object can slide over the surface without
|
|
/// going through any of the planes.
|
|
/// </summary>
|
|
file struct VelocityClipPlanes : IDisposable
|
|
{
|
|
Vector3 OrginalVelocity;
|
|
Vector3 BumpVelocity;
|
|
Vector3[] Planes;
|
|
|
|
/// <summary>
|
|
/// Maximum number of planes that can be hit
|
|
/// </summary>
|
|
public int Max { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Number of planes we're currently holding
|
|
/// </summary>
|
|
public int Count { get; private set; }
|
|
|
|
public VelocityClipPlanes( Vector3 originalVelocity, int max )
|
|
{
|
|
Max = max;
|
|
OrginalVelocity = originalVelocity;
|
|
BumpVelocity = originalVelocity;
|
|
Planes = ArrayPool<Vector3>.Shared.Rent( max );
|
|
Count = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Try to add this plane and restrain velocity to it (and its brothers)
|
|
/// </summary>
|
|
/// <returns>False if we ran out of room and should stop adding planes</returns>
|
|
public bool TryAdd( Vector3 normal, ref Vector3 velocity, float bounce )
|
|
{
|
|
if ( Count == Max )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Planes[Count++] = normal;
|
|
|
|
//
|
|
// if we only hit one plane then apply the bounce
|
|
//
|
|
if ( Count == 1 )
|
|
{
|
|
BumpVelocity = ClipVelocity( OrginalVelocity, normal, 1.0f + bounce );
|
|
OrginalVelocity = BumpVelocity;
|
|
velocity = BumpVelocity;
|
|
|
|
return true;
|
|
}
|
|
|
|
if ( TryClip( ref velocity ) )
|
|
{
|
|
// Hit the floor and the wall, go along the join
|
|
if ( Count == 2 )
|
|
{
|
|
var dir = Vector3.Cross( Planes[0], Planes[1] ).Normal;
|
|
var d = dir.Dot( velocity );
|
|
velocity = dir * d;
|
|
}
|
|
else
|
|
{
|
|
velocity = Vector3.Zero;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We're moving in the opposite direction to our
|
|
// original intention so just stop right there.
|
|
//
|
|
if ( velocity.Dot( OrginalVelocity ) <= 0 )
|
|
{
|
|
velocity = 0;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Try to clip our velocity to all the planes, so we're not travelling into them
|
|
/// Returns true if we clipped properly
|
|
/// </summary>
|
|
bool TryClip( ref Vector3 velocity )
|
|
{
|
|
for ( int i = 0; i < Count; i++ )
|
|
{
|
|
velocity = ClipVelocity( OrginalVelocity, Planes[i] );
|
|
|
|
if ( MovingTowardsAnyPlane( velocity, i ) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if we're moving towards any of our planes (except for skip)
|
|
/// </summary>
|
|
bool MovingTowardsAnyPlane( Vector3 velocity, int iSkip )
|
|
{
|
|
for ( int j = 0; j < Count; j++ )
|
|
{
|
|
if ( j == iSkip ) continue;
|
|
if ( velocity.Dot( Planes[j] ) < 0 ) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a new bump. Clears planes and resets BumpVelocity
|
|
/// </summary>
|
|
public void StartBump( Vector3 velocity )
|
|
{
|
|
BumpVelocity = velocity;
|
|
Count = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clip the velocity to the normal
|
|
/// </summary>
|
|
Vector3 ClipVelocity( Vector3 vel, Vector3 norm, float overbounce = 1.0f )
|
|
{
|
|
var backoff = Vector3.Dot( vel, norm ) * overbounce;
|
|
var o = vel - norm * backoff;
|
|
|
|
// garry: I don't totally understand how we could still
|
|
// be travelling towards the norm, but the hl2 code
|
|
// does another check here, so we're going to too.
|
|
|
|
var adjust = Vector3.Dot( o, norm );
|
|
|
|
if ( adjust < 0.0f )
|
|
{
|
|
o -= norm * adjust;
|
|
}
|
|
|
|
return o;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
ArrayPool<Vector3>.Shared.Return( Planes );
|
|
}
|
|
}
|