mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-29 00:31:05 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
1230 lines
31 KiB
C#
1230 lines
31 KiB
C#
using NativeEngine;
|
|
using System.Runtime.InteropServices;
|
|
using static Sandbox.PhysicsGroupDescription.BodyPart;
|
|
|
|
namespace Sandbox;
|
|
|
|
/// <summary>
|
|
/// Represents a physics object. An entity can have multiple physics objects. See <see cref="PhysicsGroup">PhysicsGroup</see>.
|
|
/// A physics objects consists of one or more <see cref="PhysicsShape">PhysicsShape</see>s.
|
|
/// </summary>
|
|
[Expose, ActionGraphIgnore]
|
|
public sealed partial class PhysicsBody : IHandle
|
|
{
|
|
#region IHandle
|
|
//
|
|
// A pointer to the actual native object
|
|
//
|
|
internal IPhysicsBody native;
|
|
|
|
//
|
|
// IHandle implementation
|
|
//
|
|
void IHandle.HandleInit( IntPtr ptr )
|
|
{
|
|
native = ptr;
|
|
|
|
World ??= native.GetWorld();
|
|
World.RegisterBody( this );
|
|
}
|
|
|
|
void IHandle.HandleDestroy() => native = IntPtr.Zero;
|
|
bool IHandle.HandleValid() => !native.IsNull;
|
|
#endregion
|
|
|
|
internal PhysicsBody( HandleCreationData _ ) { }
|
|
|
|
public PhysicsBody( PhysicsWorld world )
|
|
{
|
|
World = world;
|
|
|
|
using ( var h = IHandle.MakeNextHandle( this ) )
|
|
{
|
|
world.world.AddBody();
|
|
}
|
|
}
|
|
|
|
Component _component;
|
|
|
|
/// <summary>
|
|
/// The GameObject that created this body
|
|
/// </summary>
|
|
public GameObject GameObject { get; set; }
|
|
|
|
/// <summary>
|
|
/// The component that created this body
|
|
/// </summary>
|
|
public Component Component
|
|
{
|
|
get => _component;
|
|
set
|
|
{
|
|
_component = value;
|
|
GameObject = _component?.GameObject;
|
|
}
|
|
}
|
|
|
|
[Obsolete( "Use Component property" )]
|
|
public void SetComponentSource( Component c )
|
|
{
|
|
Component = c;
|
|
}
|
|
|
|
[Obsolete( "Use GameObject property" )]
|
|
public GameObject GetGameObject() => GameObject;
|
|
|
|
/// <summary>
|
|
/// The Hitbox that this physics body represents
|
|
/// </summary>
|
|
internal object Hitbox { get; set; }
|
|
|
|
/// <summary>
|
|
/// Position of this body in world coordinates.
|
|
/// </summary>
|
|
public Vector3 Position
|
|
{
|
|
get => native.GetPosition();
|
|
set
|
|
{
|
|
native.SetPosition( value );
|
|
Dirty();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The physics world this body belongs to.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public PhysicsWorld World { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Rotation of the physics body in world space.
|
|
/// </summary>
|
|
public Rotation Rotation
|
|
{
|
|
get => native.GetOrientation();
|
|
set
|
|
{
|
|
native.SetOrientation( value );
|
|
Dirty();
|
|
}
|
|
}
|
|
|
|
[Obsolete]
|
|
public float Scale => 1.0f;
|
|
|
|
/// <summary>
|
|
/// Linear velocity of this body in world space.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public Vector3 Velocity
|
|
{
|
|
get => native.GetLinearVelocity();
|
|
set => native.SetLinearVelocity( value );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Angular velocity of this body in world space.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public Vector3 AngularVelocity
|
|
{
|
|
get => native.GetAngularVelocity();
|
|
set => native.SetAngularVelocity( value );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Center of mass for this physics body in world space coordinates.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public Vector3 MassCenter
|
|
{
|
|
get => native.GetMassCenter();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Center of mass for this physics body relative to its <see cref="Position">origin</see>.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public Vector3 LocalMassCenter
|
|
{
|
|
get => native.GetLocalMassCenter();
|
|
set => native.SetLocalMassCenter( value );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is this physics body mass calculated or set directly.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public bool OverrideMassCenter
|
|
{
|
|
get => native.GetOverrideMassCenter();
|
|
set => native.SetOverrideMassCenter( value );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mass of this physics body.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public float Mass
|
|
{
|
|
get => native.GetMass();
|
|
set => native.SetMass( value );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether gravity is enabled for this body or not.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public bool GravityEnabled
|
|
{
|
|
get => native.IsGravityEnabled();
|
|
set => native.EnableGravity( value );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether to play collision sounds
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public bool EnableCollisionSounds { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Scale the gravity relative to <see cref="PhysicsWorld.Gravity"/>. 2 is double the gravity, etc.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public float GravityScale
|
|
{
|
|
get => native.GetGravityScale();
|
|
set => native.SetGravityScale( value );
|
|
}
|
|
|
|
/// <summary>
|
|
/// If true we'll create a controller for this physics body. This is useful
|
|
/// for keyframed physics objects that need to push things. The controller will
|
|
/// sweep as the entity moves, rather than teleporting the object.. which works better
|
|
/// when pushing dynamic objects etc.
|
|
/// </summary>
|
|
public bool UseController { get; set; }
|
|
|
|
/// <summary>
|
|
/// Enables Touch callbacks on all <see cref="PhysicsShape">PhysicsShapes</see> of this body.
|
|
/// Returns true if ANY of the physics shapes have touch events enabled.
|
|
/// </summary>
|
|
public bool EnableTouch
|
|
{
|
|
get => native.IsTouchEventEnabled();
|
|
set
|
|
{
|
|
if ( value )
|
|
{
|
|
native.EnableTouchEvents();
|
|
}
|
|
else
|
|
{
|
|
native.DisableTouchEvents();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets <see cref="PhysicsShape.EnableTouchPersists"/> on all shapes of this body.
|
|
/// <br/><br/>
|
|
/// Returns true if ANY of the physics shapes have persistent touch events enabled.
|
|
/// </summary>
|
|
public bool EnableTouchPersists
|
|
{
|
|
get
|
|
{
|
|
foreach ( var body in Shapes )
|
|
{
|
|
if ( body.EnableTouchPersists ) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
set
|
|
{
|
|
foreach ( var shape in Shapes )
|
|
{
|
|
shape.EnableTouchPersists = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets <see cref="PhysicsShape.EnableSolidCollisions"/> on all shapes of this body.
|
|
/// <br/><br/>
|
|
/// Returns true if ANY of the physics shapes have solid collisions enabled.
|
|
/// </summary>
|
|
public bool EnableSolidCollisions
|
|
{
|
|
get
|
|
{
|
|
foreach ( var body in Shapes )
|
|
{
|
|
if ( body.EnableSolidCollisions ) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
set
|
|
{
|
|
foreach ( var shape in Shapes )
|
|
{
|
|
shape.EnableSolidCollisions = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// cache this, since it's called so much
|
|
PhysicsBodyType? _bodyType;
|
|
|
|
/// <summary>
|
|
/// Movement type of physics body, either Static, Keyframed, Dynamic
|
|
/// Note: If this body is networked and dynamic, it will return Keyframed on the client
|
|
/// </summary>
|
|
public PhysicsBodyType BodyType
|
|
{
|
|
get
|
|
{
|
|
if ( !_bodyType.HasValue )
|
|
{
|
|
_bodyType = native.GetType_Native();
|
|
}
|
|
|
|
return _bodyType.Value;
|
|
}
|
|
set
|
|
{
|
|
if ( value == BodyType )
|
|
return;
|
|
|
|
native.SetType( value );
|
|
_bodyType = default;
|
|
|
|
Dirty();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The bodytype may change between edit and game time.
|
|
/// For navmesh generation we always need to know the bodytype at game time.
|
|
/// This override can be set to inform the navmesh generation of the correct game time bodytype.
|
|
/// </summary>
|
|
internal PhysicsBodyType? NavmeshBodyTypeOverride { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether this body is allowed to automatically go into "sleep" after a certain amount of time of inactivity.
|
|
/// <see cref="Sleeping"/> for more info on the sleep mechanic.
|
|
/// </summary>
|
|
public bool AutoSleep
|
|
{
|
|
set
|
|
{
|
|
if ( value ) native.EnableAutoSleeping();
|
|
else native.DisableAutoSleeping();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Transform of this physics body.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public Transform Transform
|
|
{
|
|
get => native.GetTransform();
|
|
set
|
|
{
|
|
var tx = value.WithScale( 1 );
|
|
if ( tx.AlmostEqual( Transform ) )
|
|
return;
|
|
|
|
native.SetTransform( tx.Position, tx.Rotation );
|
|
Dirty();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move to a new position. Unlike Transform, if you have `UseController` enabled, this will sweep the shadow
|
|
/// to the new position, rather than teleporting there.
|
|
/// </summary>
|
|
public void Move( Transform tx, float delta )
|
|
{
|
|
if ( UseController )
|
|
{
|
|
native.SetTargetTransform( tx.Position, tx.Rotation, delta );
|
|
}
|
|
else
|
|
{
|
|
bool transformChanged = !tx.AlmostEqual( Transform );
|
|
|
|
native.SetTransform( tx.Position, tx.Rotation );
|
|
|
|
if ( transformChanged )
|
|
{
|
|
Dirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// How many shapes belong to this body.
|
|
/// </summary>
|
|
public int ShapeCount => native.GetShapeCount();
|
|
|
|
/// <summary>
|
|
/// All shapes that belong to this body.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public IEnumerable<PhysicsShape> Shapes
|
|
{
|
|
get
|
|
{
|
|
var shapeCount = native.GetShapeCount();
|
|
|
|
for ( int i = 0; i < shapeCount; ++i )
|
|
{
|
|
yield return native.GetShape( i );
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a sphere shape to this body.
|
|
/// </summary>
|
|
/// <param name="center">Center of the sphere, relative to <see cref="Position"/> of this body.</param>
|
|
/// <param name="radius">Radius of the sphere.</param>
|
|
/// <param name="rebuildMass">Whether the mass should be <see cref="RebuildMass">recalculated</see> after adding the shape.</param>
|
|
/// <returns>The newly created shape, if any.</returns>
|
|
public PhysicsShape AddSphereShape( Vector3 center, float radius, bool rebuildMass = true )
|
|
{
|
|
var shape = native.AddSphereShape( center, radius );
|
|
Dirty();
|
|
return shape;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a sphere shape to this body.
|
|
/// </summary>
|
|
public PhysicsShape AddSphereShape( in Sphere sphere, bool rebuildMass = true )
|
|
{
|
|
var shape = native.AddSphereShape( sphere.Center, sphere.Radius );
|
|
Dirty();
|
|
return shape;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a capsule shape to this body.
|
|
/// </summary>
|
|
/// <param name="center">Point A of the capsule, relative to <see cref="Position"/> of this body.</param>
|
|
/// <param name="center2">Point B of the capsule, relative to <see cref="Position"/> of this body.</param>
|
|
/// <param name="radius">Radius of the capsule end caps.</param>
|
|
/// <param name="rebuildMass">Whether the mass should be <see cref="RebuildMass">recalculated</see> after adding the shape.</param>
|
|
/// <returns>The newly created shape, or null on failure.</returns>
|
|
public PhysicsShape AddCapsuleShape( Vector3 center, Vector3 center2, float radius, bool rebuildMass = true )
|
|
{
|
|
var shape = native.AddCapsuleShape( center, center2, radius );
|
|
Dirty();
|
|
return shape;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a box shape to this body.
|
|
/// </summary>
|
|
/// <param name="position">Center of the box, relative to <see cref="Position"/> of this body.</param>
|
|
/// <param name="rotation">Rotation of the box, relative to <see cref="Rotation"/> of this body.</param>
|
|
/// <param name="extent">The extents of the box. The box will extend from its center by this much in both negative and positive directions of each axis.</param>
|
|
/// <param name="rebuildMass">Whether the mass should be <see cref="RebuildMass">recalculated</see> after adding the shape.</param>
|
|
/// <returns>The newly created shape, or null on failure.</returns>
|
|
public PhysicsShape AddBoxShape( Vector3 position, Rotation rotation, Vector3 extent, bool rebuildMass = true )
|
|
{
|
|
var shape = native.AddBoxShape( position, rotation, extent.Abs() );
|
|
Dirty();
|
|
return shape;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a box shape to this body.
|
|
/// </summary>
|
|
public PhysicsShape AddBoxShape( BBox box, Rotation rotation, bool rebuildMass = true )
|
|
{
|
|
var shape = native.AddBoxShape( box.Center, rotation, box.Size * 0.5f );
|
|
Dirty();
|
|
return shape;
|
|
}
|
|
|
|
/// <inheritdoc cref="AddHullShape(Vector3, Rotation, Span{Vector3}, bool)"/>
|
|
public PhysicsShape AddHullShape( Vector3 position, Rotation rotation, List<Vector3> points, bool rebuildMass = true )
|
|
{
|
|
return AddHullShape( position, rotation, CollectionsMarshal.AsSpan( points ), rebuildMass );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a convex hull shape to this body.
|
|
/// </summary>
|
|
/// <param name="position">Center of the hull, relative to <see cref="Position"/> of this body.</param>
|
|
/// <param name="rotation">Rotation of the hull, relative to <see cref="Rotation"/> of this body.</param>
|
|
/// <param name="points">Points for the hull. They will be used to generate a convex shape.</param>
|
|
/// <param name="rebuildMass">Whether the mass should be <see cref="RebuildMass">recalculated</see> after adding the shape.</param>
|
|
/// <returns>The newly created shape, or null on failure.</returns>
|
|
public unsafe PhysicsShape AddHullShape( Vector3 position, Rotation rotation, Span<Vector3> points, bool rebuildMass = true )
|
|
{
|
|
if ( points.Length == 0 )
|
|
return null;
|
|
|
|
PhysicsShape shape;
|
|
|
|
fixed ( Vector3* points_ptr = points )
|
|
{
|
|
shape = native.AddHullShape( position, rotation, points.Length, (IntPtr)points_ptr );
|
|
}
|
|
|
|
if ( !shape.IsValid() || shape.ShapeType == PhysicsShapeType.SHAPE_SPHERE )
|
|
{
|
|
Log.Warning( "Unable to create hull shape" );
|
|
}
|
|
|
|
Dirty();
|
|
|
|
return shape;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a cylinder shape to this body.
|
|
/// </summary>
|
|
public PhysicsShape AddCylinderShape( Vector3 position, Rotation rotation, float height, float radius, int slices = 16 )
|
|
{
|
|
return AddConeShape( position, rotation, height, radius, radius, slices );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a cone shape to this body.
|
|
/// </summary>
|
|
public PhysicsShape AddConeShape( Vector3 position, Rotation rotation, float height, float radius1, float radius2 = 0.0f, int slices = 16 )
|
|
{
|
|
slices = slices.Clamp( 4, 128 );
|
|
|
|
var vertexCount = 2 * slices;
|
|
var points = new Vector3[vertexCount];
|
|
|
|
var alpha = 0.0f;
|
|
var deltaAlpha = MathF.PI * 2 / slices;
|
|
var halfHeight = height * 0.5f;
|
|
|
|
for ( int i = 0; i < slices; ++i )
|
|
{
|
|
var sinAlpha = MathF.Sin( alpha );
|
|
var cosAlpha = MathF.Cos( alpha );
|
|
|
|
points[2 * i + 0] = new Vector3( radius1 * cosAlpha, radius1 * sinAlpha, -halfHeight );
|
|
points[2 * i + 1] = new Vector3( radius2 * cosAlpha, radius2 * sinAlpha, halfHeight );
|
|
|
|
alpha += deltaAlpha;
|
|
}
|
|
|
|
return AddHullShape( position, rotation, points );
|
|
}
|
|
|
|
/// <inheritdoc cref="AddMeshShape(Span{Vector3}, Span{int})"/>
|
|
public PhysicsShape AddMeshShape( List<Vector3> vertices, List<int> indices )
|
|
{
|
|
return AddMeshShape( CollectionsMarshal.AsSpan( vertices ), CollectionsMarshal.AsSpan( indices ) );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a mesh type shape to this physics body. Mesh shapes cannot be physically simulated!
|
|
/// </summary>
|
|
/// <param name="vertices">Vertices of the mesh.</param>
|
|
/// <param name="indices">Indices of the mesh.</param>
|
|
/// <returns>The created shape, or null on failure.</returns>
|
|
public unsafe PhysicsShape AddMeshShape( Span<Vector3> vertices, Span<int> indices )
|
|
{
|
|
if ( vertices.Length == 0 )
|
|
return null;
|
|
|
|
if ( indices.Length == 0 )
|
|
return null;
|
|
|
|
var vertexCount = vertices.Length;
|
|
|
|
foreach ( var i in indices )
|
|
{
|
|
if ( i < 0 || i >= vertexCount )
|
|
throw new ArgumentOutOfRangeException( $"Index ({i}) out of range ({vertexCount - 1})" );
|
|
}
|
|
|
|
PhysicsShape shape;
|
|
|
|
fixed ( Vector3* vertices_ptr = vertices )
|
|
fixed ( int* indices_ptr = indices )
|
|
{
|
|
shape = native.AddMeshShape( vertexCount, (IntPtr)vertices_ptr, indices.Length, (IntPtr)indices_ptr, 0 );
|
|
}
|
|
|
|
if ( !shape.IsValid() || shape.ShapeType == PhysicsShapeType.SHAPE_SPHERE )
|
|
{
|
|
Log.Warning( "Unable to create mesh shape" );
|
|
}
|
|
|
|
Dirty();
|
|
|
|
return shape;
|
|
}
|
|
|
|
public unsafe PhysicsShape AddHeightFieldShape( ushort[] heights, byte[] materials, int sizeX, int sizeY, float sizeScale, float heightScale )
|
|
{
|
|
return AddHeightFieldShape( heights, materials, sizeX, sizeY, sizeScale, heightScale, 0 );
|
|
}
|
|
|
|
internal unsafe PhysicsShape AddHeightFieldShape( ushort[] heights, byte[] materials, int sizeX, int sizeY, float sizeScale, float heightScale, int materialCount )
|
|
{
|
|
if ( heights == null )
|
|
throw new ArgumentException( "Height data is null" );
|
|
|
|
var cellCount = sizeX * sizeY;
|
|
if ( cellCount <= 0 )
|
|
throw new ArgumentOutOfRangeException( "Size needs to be non zero" );
|
|
|
|
if ( heights.Length != cellCount )
|
|
throw new ArgumentOutOfRangeException( $"Height data length is {heights.Length}, should be {cellCount}" );
|
|
|
|
if ( materials != null && materials.Length != cellCount )
|
|
throw new ArgumentOutOfRangeException( $"Material data length is {materials.Length}, should be {cellCount}" );
|
|
|
|
fixed ( ushort* pHeights = heights )
|
|
fixed ( byte* pMaterials = materials )
|
|
{
|
|
var shape = native.AddHeightFieldShape(
|
|
(IntPtr)pHeights,
|
|
(IntPtr)pMaterials,
|
|
sizeX, sizeY,
|
|
sizeScale, heightScale,
|
|
materialCount );
|
|
|
|
Dirty();
|
|
|
|
return shape;
|
|
}
|
|
}
|
|
|
|
[Obsolete]
|
|
public PhysicsShape AddCloneShape( PhysicsShape shape )
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all physics shapes, but not the physics body itself.
|
|
/// </summary>
|
|
public void ClearShapes()
|
|
{
|
|
native.PurgeShapes();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called from Shape.Remove()
|
|
/// </summary>
|
|
internal void RemoveShape( PhysicsShape shape )
|
|
{
|
|
if ( !shape.IsValid() )
|
|
return;
|
|
|
|
if ( !this.IsValid() )
|
|
return;
|
|
|
|
if ( !World.IsValid() )
|
|
return;
|
|
|
|
native.RemoveShape( shape );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Meant to be only used on <b>dynamic</b> bodies, rebuilds mass from all shapes of this body based on their volume and <see cref="Surface">physics properties</see>, for cases where they may have changed.
|
|
/// </summary>
|
|
public void RebuildMass() => native.BuildMass();
|
|
|
|
/// <summary>
|
|
/// Completely removes this physics body.
|
|
/// </summary>
|
|
public void Remove()
|
|
{
|
|
if ( !this.IsValid() ) return;
|
|
|
|
if ( World.IsValid() )
|
|
{
|
|
World.UnregisterBody( this );
|
|
}
|
|
|
|
native = default;
|
|
World = default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies instant linear impulse (i.e. a bullet impact) to this body at its center of mass.
|
|
/// For continuous force (i.e. a moving car), use <see cref="ApplyForce"/>
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public void ApplyImpulse( Vector3 impulse )
|
|
{
|
|
native.ApplyLinearImpulse( impulse );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies instant linear impulse (i.e. a bullet impact) to this body at given position.
|
|
/// For continuous force (i.e. a moving car), use <see cref="ApplyForceAt"/>
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public void ApplyImpulseAt( Vector3 position, Vector3 velocity )
|
|
{
|
|
native.ApplyLinearImpulseAtWorldSpace( velocity, position );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies instant angular impulse (i.e. a bullet impact) to this body.
|
|
/// For continuous force (i.e. a moving car), use <see cref="ApplyTorque"/>
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public void ApplyAngularImpulse( Vector3 impulse )
|
|
{
|
|
native.ApplyAngularImpulse( impulse );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies force to this body at the center of mass.
|
|
/// This force will only be applied on the next physics frame and is scaled with physics timestep.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public void ApplyForce( Vector3 force ) => native.ApplyForce( force );
|
|
|
|
/// <summary>
|
|
/// Applies force to this body at given position.
|
|
/// This force will only be applied on the next physics frame and is scaled with physics timestep.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public void ApplyForceAt( Vector3 position, Vector3 force ) => native.ApplyForceAt( force, position );
|
|
|
|
/// <summary>
|
|
/// Applies angular velocity to this body.
|
|
/// This force will only be applied on the next physics frame and is scaled with physics timestep.
|
|
/// </summary>
|
|
/// <param name="force"></param>
|
|
[ActionGraphInclude]
|
|
public void ApplyTorque( Vector3 force ) => native.ApplyTorque( force );
|
|
|
|
/// <summary>
|
|
/// Clear accumulated linear forces (<see cref="ApplyForce"/> and <see cref="ApplyForceAt"/>) during this physics frame that were not yet applied to the physics body.
|
|
/// </summary>
|
|
public void ClearForces()
|
|
{
|
|
native.ClearForces();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear accumulated torque (angular force, <see cref="ApplyTorque"/>) during this physics frame that were not yet applied to the physics body.
|
|
/// </summary>
|
|
public void ClearTorque()
|
|
{
|
|
native.ClearTorque();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the world space velocity of a point of the object. This is useful for objects rotating around their own axis/origin.
|
|
/// </summary>
|
|
/// <param name="point">The point to test, in world coordinates.</param>
|
|
/// <returns>Velocity at the given point.</returns>
|
|
[ActionGraphInclude, Pure]
|
|
public Vector3 GetVelocityAtPoint( Vector3 point )
|
|
{
|
|
return native.GetVelocityAtPoint( point );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether this body is enabled or not. Disables collisions, physics simulation, touch events, trace queries, etc.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public bool Enabled
|
|
{
|
|
get => native.IsEnabled();
|
|
set
|
|
{
|
|
if ( native.IsNull )
|
|
return;
|
|
|
|
if ( value ) native.Enable();
|
|
else native.Disable();
|
|
|
|
Dirty();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Controls physics simulation on this body.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public bool MotionEnabled
|
|
{
|
|
get => BodyType == PhysicsBodyType.Dynamic;
|
|
set
|
|
{
|
|
if ( value )
|
|
{
|
|
BodyType = PhysicsBodyType.Dynamic;
|
|
|
|
return;
|
|
}
|
|
|
|
// Clear velocity when disabling motion.
|
|
// We do this here (not in BodyType setter) in case preserving velocity is intentional.
|
|
// Also, disabling motion implies that all motion stops.
|
|
if ( BodyType == PhysicsBodyType.Dynamic )
|
|
{
|
|
Velocity = 0;
|
|
AngularVelocity = 0;
|
|
}
|
|
|
|
BodyType = PhysicsBodyType.Keyframed;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Physics bodies automatically go to sleep after a certain amount of time of inactivity to save on performance.
|
|
/// You can use this to wake the body up, or prematurely send it to sleep.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public bool Sleeping
|
|
{
|
|
get => native.IsSleeping();
|
|
|
|
set
|
|
{
|
|
if ( value ) native.Sleep();
|
|
else native.Wake();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// If enabled, this physics body will move slightly ahead each frame based on its velocities.
|
|
/// </summary>
|
|
[Obsolete( "No longer exists" )]
|
|
public bool SpeculativeContactEnabled
|
|
{
|
|
get => false;
|
|
set { }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The physics body we are attached to, if any
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public PhysicsBody Parent { get; set; }
|
|
|
|
/// <summary>
|
|
/// A convenience property, returns <see cref="Parent">Parent</see>, or if there is no parent, returns itself.
|
|
/// </summary>
|
|
public PhysicsBody SelfOrParent => Parent ?? this;
|
|
|
|
/// <summary>
|
|
/// The physics group we belong to.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public PhysicsGroup PhysicsGroup
|
|
{
|
|
get
|
|
{
|
|
if ( native.IsNull ) return null;
|
|
return native.GetAggregate();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the closest point to the given one between all shapes of this body.
|
|
/// </summary>
|
|
/// <param name="vec">Input position.</param>
|
|
/// <returns>The closest possible position on the surface of the physics body to the given position.</returns>
|
|
[ActionGraphInclude, Pure]
|
|
public Vector3 FindClosestPoint( Vector3 vec )
|
|
{
|
|
return native.GetClosestPoint( vec );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generic linear damping, i.e. how much the physics body will slow down on its own.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public float LinearDamping
|
|
{
|
|
get => native.GetLinearDamping();
|
|
set => native.SetLinearDamping( value );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generic angular damping, i.e. how much the physics body will slow down on its own.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public float AngularDamping
|
|
{
|
|
get => native.GetAngularDamping();
|
|
set => native.SetAngularDamping( value );
|
|
}
|
|
|
|
[Obsolete]
|
|
public float LinearDrag
|
|
{
|
|
get => default;
|
|
set { }
|
|
}
|
|
|
|
[Obsolete]
|
|
public float AngularDrag
|
|
{
|
|
get => default;
|
|
set { }
|
|
}
|
|
|
|
[Obsolete]
|
|
public bool DragEnabled
|
|
{
|
|
get => default;
|
|
set { }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The diagonal elements of the local inertia tensor matrix.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public Vector3 Inertia
|
|
{
|
|
get => native.GetLocalInertiaVector();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The orientation of the principal axes of local inertia tensor matrix.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public Rotation InertiaRotation
|
|
{
|
|
get => native.GetLocalInertiaOrientation();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the inertia tensor using the given moments and rotation.
|
|
/// </summary>
|
|
/// <param name="inertia">Principal moments (Ixx, Iyy, Izz).</param>
|
|
/// <param name="rotation">Rotation of the principal axes.</param>
|
|
public void SetInertiaTensor( Vector3 inertia, Rotation rotation )
|
|
{
|
|
native.SetLocalInertia( inertia, rotation );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the inertia tensor to its calculated values.
|
|
/// </summary>
|
|
public void ResetInertiaTensor()
|
|
{
|
|
native.ResetLocalInertia();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns Axis-Aligned Bounding Box (AABB) of this physics body.
|
|
/// </summary>
|
|
[ActionGraphInclude, Pure]
|
|
public BBox GetBounds()
|
|
{
|
|
return native.BuildBounds();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns average of densities for all physics shapes of this body. This is based on <see cref="PhysicsShape.SurfaceMaterial"/> of each shape.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public float Density
|
|
{
|
|
get => native.GetDensity();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Time since last water splash effect. Used internally.
|
|
/// </summary>
|
|
public RealTimeSince LastWaterEffect { get; set; }
|
|
|
|
/// <summary>
|
|
/// Sets <see cref="PhysicsShape.SurfaceMaterial"/> on all child <see cref="PhysicsShape">PhysicsShape</see>s.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The most commonly occurring surface name between all <see cref="PhysicsShape">PhysicsShape</see>s of this <see cref="PhysicsShape">PhysicsBody</see>.
|
|
/// </returns>
|
|
[ActionGraphInclude]
|
|
public string SurfaceMaterial
|
|
{
|
|
get
|
|
{
|
|
if ( !Shapes.Any() ) return "default";
|
|
|
|
return Shapes.Select( s => s.SurfaceMaterial )
|
|
.GroupBy( v => v )
|
|
.OrderByDescending( g => g.Count() )
|
|
.First().Key;
|
|
}
|
|
|
|
set
|
|
{
|
|
native.SetMaterialIndex( value );
|
|
}
|
|
}
|
|
|
|
Surface _surface;
|
|
|
|
public Surface Surface
|
|
{
|
|
get
|
|
{
|
|
// todo - if _surface is null, look up from GetMaterialName()
|
|
return _surface;
|
|
}
|
|
set
|
|
{
|
|
if ( _surface == value ) return;
|
|
|
|
_surface = value;
|
|
native.SetMaterialIndex( _surface?.ResourceName );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convenience function that returns a <see cref="PhysicsPoint"/> from a position relative to this body.
|
|
/// </summary>
|
|
public PhysicsPoint LocalPoint( Vector3 p ) => PhysicsPoint.Local( this, p );
|
|
|
|
/// <summary>
|
|
/// Convenience function that returns a <see cref="PhysicsPoint"/> for this body from a world space position.
|
|
/// </summary>
|
|
public PhysicsPoint WorldPoint( Vector3 p ) => PhysicsPoint.World( this, p );
|
|
|
|
/// <summary>
|
|
/// Returns a <see cref="PhysicsPoint"/> at the center of mass of this body.
|
|
/// </summary>
|
|
public PhysicsPoint MassCenterPoint() => PhysicsPoint.Local( this, LocalMassCenter );
|
|
|
|
|
|
/// <summary>
|
|
/// What is this body called in the group?
|
|
/// </summary>
|
|
public string GroupName
|
|
{
|
|
get
|
|
{
|
|
// should we be caching this stuff? When should it invalidate?
|
|
return PhysicsGroup?.native.GetBodyName( GroupIndex );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the index of this body in its PhysicsGroup
|
|
/// </summary>
|
|
public int GroupIndex
|
|
{
|
|
get
|
|
{
|
|
// should we be caching this stuff? When should it invalidate?
|
|
return PhysicsGroup?.native.GetBodyIndex( this ) ?? 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if another body overlaps us, ignoring all collision rules
|
|
/// </summary>
|
|
public bool CheckOverlap( PhysicsBody body )
|
|
{
|
|
if ( !body.IsValid() )
|
|
return false;
|
|
|
|
return CheckOverlap( body, body.Transform );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if another body overlaps us at a given transform, ignoring all collision rules
|
|
/// </summary>
|
|
public bool CheckOverlap( PhysicsBody body, Transform transform )
|
|
{
|
|
if ( !this.IsValid() || !body.IsValid() )
|
|
return false;
|
|
|
|
return native.CheckOverlap( body, transform );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if there's any contact points with another body
|
|
/// </summary>
|
|
internal bool IsTouching( PhysicsBody body, bool triggersOnly )
|
|
{
|
|
if ( !body.IsValid() )
|
|
return false;
|
|
|
|
return native.IsTouching( body, triggersOnly );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if there's any contact points with another shape
|
|
/// </summary>
|
|
internal bool IsTouching( PhysicsShape shape, bool triggersOnly )
|
|
{
|
|
if ( !shape.IsValid() )
|
|
return false;
|
|
|
|
return native.IsTouching( shape, triggersOnly );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a shape from a physics hull
|
|
/// </summary>
|
|
public PhysicsShape AddShape( HullPart part, Transform transform, bool rebuildMass = true )
|
|
{
|
|
var shape = native.AddHullShape( part.hull, transform );
|
|
|
|
if ( !shape.IsValid() || shape.ShapeType == PhysicsShapeType.SHAPE_SPHERE )
|
|
{
|
|
Log.Warning( "Unable to create hull shape" );
|
|
}
|
|
|
|
Dirty();
|
|
|
|
return shape;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a shape from a mesh hull
|
|
/// </summary>
|
|
public PhysicsShape AddShape( MeshPart part, Transform transform, bool convertToHull, bool rebuildMass = true )
|
|
{
|
|
PhysicsShape shape;
|
|
|
|
if ( convertToHull )
|
|
{
|
|
shape = native.AddHullShape( part.mesh, transform );
|
|
}
|
|
else
|
|
{
|
|
shape = native.AddMeshShape( part.mesh, transform, part.Surfaces is null ? 0 : part.Surfaces.Length );
|
|
}
|
|
|
|
if ( !shape.IsValid() || shape.ShapeType == PhysicsShapeType.SHAPE_SPHERE )
|
|
{
|
|
Log.Warning( $"Unable to create {(convertToHull ? "hull" : "mesh")} shape" );
|
|
}
|
|
|
|
Dirty();
|
|
|
|
return shape;
|
|
}
|
|
|
|
public Action<PhysicsIntersection> OnIntersectionStart { get; set; }
|
|
public Action<PhysicsIntersection> OnIntersectionUpdate { get; set; }
|
|
public Action<PhysicsIntersectionEnd> OnIntersectionEnd { get; set; }
|
|
internal Action<PhysicsIntersection> OnTriggerBegin { get; set; }
|
|
internal Action<PhysicsIntersectionEnd> OnTriggerEnd { get; set; }
|
|
|
|
/// <summary>
|
|
/// Transform, on previous step
|
|
/// </summary>
|
|
Transform prevStepTransform;
|
|
float prevStepTime;
|
|
|
|
/// <summary>
|
|
/// Transform on current step
|
|
/// </summary>
|
|
Transform stepTransform;
|
|
float stepTime;
|
|
|
|
|
|
/// <summary>
|
|
/// Called on each active body after a "step"
|
|
/// </summary>
|
|
internal void OnActive( in Transform transform, in Vector3 velocity, in Vector3 linearVelocity )
|
|
{
|
|
prevStepTime = stepTime;
|
|
prevStepTransform = stepTime > 0 ? stepTransform : transform;
|
|
|
|
stepTransform = transform;
|
|
stepTime = World.CurrentTime;
|
|
|
|
Dirty();
|
|
}
|
|
|
|
/// <summary>
|
|
/// When the physics world is run at a fixed timestep, getting the positions of bodies will not be smooth.
|
|
/// You can use this function to get the lerped position between steps, to make things super awesome.
|
|
/// </summary>
|
|
public Transform GetLerpedTransform( float time )
|
|
{
|
|
if ( stepTime == 0 )
|
|
return Transform;
|
|
|
|
// lerp gap is too big
|
|
if ( stepTime - prevStepTime > 0.5f )
|
|
return Transform;
|
|
|
|
time -= World.CurrentDelta;
|
|
|
|
var delta = MathX.Remap( time, prevStepTime, stepTime, 0.0f, 1.0f );
|
|
return Transform.Lerp( prevStepTransform, stepTransform, delta, true );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move body to this position in a way that cooperates with the physics system. This is quite
|
|
/// good for things like grabbing and moving objects.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public void SmoothMove( in Vector3 position, float timeToArrive, float timeDelta )
|
|
{
|
|
var velocity = Velocity;
|
|
Vector3.SmoothDamp( Position, position, ref velocity, timeToArrive, timeDelta );
|
|
Velocity = velocity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move body to this position in a way that cooperates with the physics system. This is quite
|
|
/// good for things like grabbing and moving objects.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public void SmoothMove( in Transform transform, float smoothTime, float timeDelta )
|
|
{
|
|
SmoothMove( transform.Position, smoothTime, timeDelta );
|
|
SmoothRotate( transform.Rotation, smoothTime, timeDelta );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rotate the body to this position in a way that cooperates with the physics system.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public void SmoothRotate( in Rotation rotation, float smoothTime, float timeDelta )
|
|
{
|
|
var angVelocity = AngularVelocity;
|
|
Rotation.SmoothDamp( Rotation, rotation, ref angVelocity, smoothTime, timeDelta );
|
|
AngularVelocity = angVelocity;
|
|
}
|
|
|
|
void Dirty()
|
|
{
|
|
OnDirty?.Invoke();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when anything significant changed about this physics object. Like its position,
|
|
/// or its enabled status.
|
|
/// </summary>
|
|
internal Action OnDirty;
|
|
|
|
|
|
internal HashSet<Joint> Joints = new HashSet<Joint>();
|
|
|
|
internal void AddJoint( Joint joint )
|
|
{
|
|
Joints.Add( joint );
|
|
}
|
|
|
|
internal void RemoveJoint( Joint joint )
|
|
{
|
|
Joints.Remove( joint );
|
|
}
|
|
}
|