using NativeEngine;
using System.Runtime.InteropServices;
using static Sandbox.PhysicsGroupDescription.BodyPart;
namespace Sandbox;
///
/// Represents a physics object. An entity can have multiple physics objects. See PhysicsGroup.
/// A physics objects consists of one or more PhysicsShapes.
///
[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;
///
/// The GameObject that created this body
///
public GameObject GameObject { get; set; }
///
/// The component that created this body
///
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;
///
/// The Hitbox that this physics body represents
///
internal object Hitbox { get; set; }
///
/// Position of this body in world coordinates.
///
public Vector3 Position
{
get => native.GetPosition();
set
{
native.SetPosition( value );
Dirty();
}
}
///
/// The physics world this body belongs to.
///
[ActionGraphInclude]
public PhysicsWorld World { get; internal set; }
///
/// Rotation of the physics body in world space.
///
public Rotation Rotation
{
get => native.GetOrientation();
set
{
native.SetOrientation( value );
Dirty();
}
}
[Obsolete]
public float Scale => 1.0f;
///
/// Linear velocity of this body in world space.
///
[ActionGraphInclude]
public Vector3 Velocity
{
get => native.GetLinearVelocity();
set => native.SetLinearVelocity( value );
}
///
/// Angular velocity of this body in world space.
///
[ActionGraphInclude]
public Vector3 AngularVelocity
{
get => native.GetAngularVelocity();
set => native.SetAngularVelocity( value );
}
///
/// Center of mass for this physics body in world space coordinates.
///
[ActionGraphInclude]
public Vector3 MassCenter
{
get => native.GetMassCenter();
}
///
/// Center of mass for this physics body relative to its origin.
///
[ActionGraphInclude]
public Vector3 LocalMassCenter
{
get => native.GetLocalMassCenter();
set => native.SetLocalMassCenter( value );
}
///
/// Is this physics body mass calculated or set directly.
///
[ActionGraphInclude]
public bool OverrideMassCenter
{
get => native.GetOverrideMassCenter();
set => native.SetOverrideMassCenter( value );
}
///
/// Mass of this physics body.
///
[ActionGraphInclude]
public float Mass
{
get => native.GetMass();
set => native.SetMass( value );
}
///
/// Whether gravity is enabled for this body or not.
///
[ActionGraphInclude]
public bool GravityEnabled
{
get => native.IsGravityEnabled();
set => native.EnableGravity( value );
}
///
/// Whether to play collision sounds
///
[ActionGraphInclude]
public bool EnableCollisionSounds { get; set; } = true;
///
/// Scale the gravity relative to . 2 is double the gravity, etc.
///
[ActionGraphInclude]
public float GravityScale
{
get => native.GetGravityScale();
set => native.SetGravityScale( value );
}
///
/// 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.
///
public bool UseController { get; set; }
///
/// Enables Touch callbacks on all PhysicsShapes of this body.
/// Returns true if ANY of the physics shapes have touch events enabled.
///
public bool EnableTouch
{
get => native.IsTouchEventEnabled();
set
{
if ( value )
{
native.EnableTouchEvents();
}
else
{
native.DisableTouchEvents();
}
}
}
///
/// Sets on all shapes of this body.
///
/// Returns true if ANY of the physics shapes have persistent touch events enabled.
///
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;
}
}
}
///
/// Sets on all shapes of this body.
///
/// Returns true if ANY of the physics shapes have solid collisions enabled.
///
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;
///
/// Movement type of physics body, either Static, Keyframed, Dynamic
/// Note: If this body is networked and dynamic, it will return Keyframed on the client
///
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();
}
}
///
/// 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.
///
internal PhysicsBodyType? NavmeshBodyTypeOverride { get; set; }
///
/// Whether this body is allowed to automatically go into "sleep" after a certain amount of time of inactivity.
/// for more info on the sleep mechanic.
///
public bool AutoSleep
{
set
{
if ( value ) native.EnableAutoSleeping();
else native.DisableAutoSleeping();
}
}
///
/// Transform of this physics body.
///
[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();
}
}
///
/// 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.
///
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();
}
}
}
///
/// How many shapes belong to this body.
///
public int ShapeCount => native.GetShapeCount();
///
/// All shapes that belong to this body.
///
[ActionGraphInclude]
public IEnumerable Shapes
{
get
{
var shapeCount = native.GetShapeCount();
for ( int i = 0; i < shapeCount; ++i )
{
yield return native.GetShape( i );
}
}
}
///
/// Add a sphere shape to this body.
///
/// Center of the sphere, relative to of this body.
/// Radius of the sphere.
/// Whether the mass should be recalculated after adding the shape.
/// The newly created shape, if any.
public PhysicsShape AddSphereShape( Vector3 center, float radius, bool rebuildMass = true )
{
var shape = native.AddSphereShape( center, radius );
Dirty();
return shape;
}
///
/// Add a sphere shape to this body.
///
public PhysicsShape AddSphereShape( in Sphere sphere, bool rebuildMass = true )
{
var shape = native.AddSphereShape( sphere.Center, sphere.Radius );
Dirty();
return shape;
}
///
/// Add a capsule shape to this body.
///
/// Point A of the capsule, relative to of this body.
/// Point B of the capsule, relative to of this body.
/// Radius of the capsule end caps.
/// Whether the mass should be recalculated after adding the shape.
/// The newly created shape, or null on failure.
public PhysicsShape AddCapsuleShape( Vector3 center, Vector3 center2, float radius, bool rebuildMass = true )
{
var shape = native.AddCapsuleShape( center, center2, radius );
Dirty();
return shape;
}
///
/// Add a box shape to this body.
///
/// Center of the box, relative to of this body.
/// Rotation of the box, relative to of this body.
/// The extents of the box. The box will extend from its center by this much in both negative and positive directions of each axis.
/// Whether the mass should be recalculated after adding the shape.
/// The newly created shape, or null on failure.
public PhysicsShape AddBoxShape( Vector3 position, Rotation rotation, Vector3 extent, bool rebuildMass = true )
{
var shape = native.AddBoxShape( position, rotation, extent.Abs() );
Dirty();
return shape;
}
///
/// Add a box shape to this body.
///
public PhysicsShape AddBoxShape( BBox box, Rotation rotation, bool rebuildMass = true )
{
var shape = native.AddBoxShape( box.Center, rotation, box.Size * 0.5f );
Dirty();
return shape;
}
///
public PhysicsShape AddHullShape( Vector3 position, Rotation rotation, List points, bool rebuildMass = true )
{
return AddHullShape( position, rotation, CollectionsMarshal.AsSpan( points ), rebuildMass );
}
///
/// Add a convex hull shape to this body.
///
/// Center of the hull, relative to of this body.
/// Rotation of the hull, relative to of this body.
/// Points for the hull. They will be used to generate a convex shape.
/// Whether the mass should be recalculated after adding the shape.
/// The newly created shape, or null on failure.
public unsafe PhysicsShape AddHullShape( Vector3 position, Rotation rotation, Span 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;
}
///
/// Add a cylinder shape to this body.
///
public PhysicsShape AddCylinderShape( Vector3 position, Rotation rotation, float height, float radius, int slices = 16 )
{
return AddConeShape( position, rotation, height, radius, radius, slices );
}
///
/// Add a cone shape to this body.
///
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 );
}
///
public PhysicsShape AddMeshShape( List vertices, List indices )
{
return AddMeshShape( CollectionsMarshal.AsSpan( vertices ), CollectionsMarshal.AsSpan( indices ) );
}
///
/// Adds a mesh type shape to this physics body. Mesh shapes cannot be physically simulated!
///
/// Vertices of the mesh.
/// Indices of the mesh.
/// The created shape, or null on failure.
public unsafe PhysicsShape AddMeshShape( Span vertices, Span 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;
}
///
/// Remove all physics shapes, but not the physics body itself.
///
public void ClearShapes()
{
native.PurgeShapes();
}
///
/// Called from Shape.Remove()
///
internal void RemoveShape( PhysicsShape shape )
{
if ( !shape.IsValid() )
return;
if ( !this.IsValid() )
return;
if ( !World.IsValid() )
return;
native.RemoveShape( shape );
}
///
/// Meant to be only used on dynamic bodies, rebuilds mass from all shapes of this body based on their volume and physics properties, for cases where they may have changed.
///
public void RebuildMass() => native.BuildMass();
///
/// Completely removes this physics body.
///
public void Remove()
{
if ( !this.IsValid() ) return;
if ( World.IsValid() )
{
World.UnregisterBody( this );
}
native = default;
World = default;
}
///
/// 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
///
[ActionGraphInclude]
public void ApplyImpulse( Vector3 impulse )
{
native.ApplyLinearImpulse( impulse );
}
///
/// Applies instant linear impulse (i.e. a bullet impact) to this body at given position.
/// For continuous force (i.e. a moving car), use
///
[ActionGraphInclude]
public void ApplyImpulseAt( Vector3 position, Vector3 velocity )
{
native.ApplyLinearImpulseAtWorldSpace( velocity, position );
}
///
/// Applies instant angular impulse (i.e. a bullet impact) to this body.
/// For continuous force (i.e. a moving car), use
///
[ActionGraphInclude]
public void ApplyAngularImpulse( Vector3 impulse )
{
native.ApplyAngularImpulse( impulse );
}
///
/// 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.
///
[ActionGraphInclude]
public void ApplyForce( Vector3 force ) => native.ApplyForce( force );
///
/// 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.
///
[ActionGraphInclude]
public void ApplyForceAt( Vector3 position, Vector3 force ) => native.ApplyForceAt( force, position );
///
/// Applies angular velocity to this body.
/// This force will only be applied on the next physics frame and is scaled with physics timestep.
///
///
[ActionGraphInclude]
public void ApplyTorque( Vector3 force ) => native.ApplyTorque( force );
///
/// Clear accumulated linear forces ( and ) during this physics frame that were not yet applied to the physics body.
///
public void ClearForces()
{
native.ClearForces();
}
///
/// Clear accumulated torque (angular force, ) during this physics frame that were not yet applied to the physics body.
///
public void ClearTorque()
{
native.ClearTorque();
}
///
/// Returns the world space velocity of a point of the object. This is useful for objects rotating around their own axis/origin.
///
/// The point to test, in world coordinates.
/// Velocity at the given point.
[ActionGraphInclude, Pure]
public Vector3 GetVelocityAtPoint( Vector3 point )
{
return native.GetVelocityAtPoint( point );
}
///
/// Whether this body is enabled or not. Disables collisions, physics simulation, touch events, trace queries, etc.
///
[ActionGraphInclude]
public bool Enabled
{
get => native.IsEnabled();
set
{
if ( native.IsNull )
return;
if ( value ) native.Enable();
else native.Disable();
Dirty();
}
}
///
/// Controls physics simulation on this body.
///
[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;
}
}
///
/// 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.
///
[ActionGraphInclude]
public bool Sleeping
{
get => native.IsSleeping();
set
{
if ( value ) native.Sleep();
else native.Wake();
}
}
///
/// If enabled, this physics body will move slightly ahead each frame based on its velocities.
///
[Obsolete( "No longer exists" )]
public bool SpeculativeContactEnabled
{
get => false;
set { }
}
///
/// The physics body we are attached to, if any
///
[ActionGraphInclude]
public PhysicsBody Parent { get; set; }
///
/// A convenience property, returns Parent, or if there is no parent, returns itself.
///
public PhysicsBody SelfOrParent => Parent ?? this;
///
/// The physics group we belong to.
///
[ActionGraphInclude]
public PhysicsGroup PhysicsGroup
{
get
{
if ( native.IsNull ) return null;
return native.GetAggregate();
}
}
///
/// Returns the closest point to the given one between all shapes of this body.
///
/// Input position.
/// The closest possible position on the surface of the physics body to the given position.
[ActionGraphInclude, Pure]
public Vector3 FindClosestPoint( Vector3 vec )
{
return native.GetClosestPoint( vec );
}
///
/// Generic linear damping, i.e. how much the physics body will slow down on its own.
///
[ActionGraphInclude]
public float LinearDamping
{
get => native.GetLinearDamping();
set => native.SetLinearDamping( value );
}
///
/// Generic angular damping, i.e. how much the physics body will slow down on its own.
///
[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 { }
}
///
/// The diagonal elements of the local inertia tensor matrix.
///
[ActionGraphInclude]
public Vector3 Inertia
{
get => native.GetLocalInertiaVector();
}
///
/// The orientation of the principal axes of local inertia tensor matrix.
///
[ActionGraphInclude]
public Rotation InertiaRotation
{
get => native.GetLocalInertiaOrientation();
}
///
/// Sets the inertia tensor using the given moments and rotation.
///
/// Principal moments (Ixx, Iyy, Izz).
/// Rotation of the principal axes.
public void SetInertiaTensor( Vector3 inertia, Rotation rotation )
{
native.SetLocalInertia( inertia, rotation );
}
///
/// Resets the inertia tensor to its calculated values.
///
public void ResetInertiaTensor()
{
native.ResetLocalInertia();
}
///
/// Returns Axis-Aligned Bounding Box (AABB) of this physics body.
///
[ActionGraphInclude, Pure]
public BBox GetBounds()
{
return native.BuildBounds();
}
///
/// Returns average of densities for all physics shapes of this body. This is based on of each shape.
///
[ActionGraphInclude]
public float Density
{
get => native.GetDensity();
}
///
/// Time since last water splash effect. Used internally.
///
public RealTimeSince LastWaterEffect { get; set; }
///
/// Sets on all child PhysicsShapes.
///
///
/// The most commonly occurring surface name between all PhysicsShapes of this PhysicsBody.
///
[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 );
}
}
///
/// Convenience function that returns a from a position relative to this body.
///
public PhysicsPoint LocalPoint( Vector3 p ) => PhysicsPoint.Local( this, p );
///
/// Convenience function that returns a for this body from a world space position.
///
public PhysicsPoint WorldPoint( Vector3 p ) => PhysicsPoint.World( this, p );
///
/// Returns a at the center of mass of this body.
///
public PhysicsPoint MassCenterPoint() => PhysicsPoint.Local( this, LocalMassCenter );
///
/// What is this body called in the group?
///
public string GroupName
{
get
{
// should we be caching this stuff? When should it invalidate?
return PhysicsGroup?.native.GetBodyName( GroupIndex );
}
}
///
/// Return the index of this body in its PhysicsGroup
///
public int GroupIndex
{
get
{
// should we be caching this stuff? When should it invalidate?
return PhysicsGroup?.native.GetBodyIndex( this ) ?? 0;
}
}
///
/// Checks if another body overlaps us, ignoring all collision rules
///
public bool CheckOverlap( PhysicsBody body )
{
if ( !body.IsValid() )
return false;
return CheckOverlap( body, body.Transform );
}
///
/// Checks if another body overlaps us at a given transform, ignoring all collision rules
///
public bool CheckOverlap( PhysicsBody body, Transform transform )
{
if ( !this.IsValid() || !body.IsValid() )
return false;
return native.CheckOverlap( body, transform );
}
///
/// Checks if there's any contact points with another body
///
internal bool IsTouching( PhysicsBody body, bool triggersOnly )
{
if ( !body.IsValid() )
return false;
return native.IsTouching( body, triggersOnly );
}
///
/// Checks if there's any contact points with another shape
///
internal bool IsTouching( PhysicsShape shape, bool triggersOnly )
{
if ( !shape.IsValid() )
return false;
return native.IsTouching( shape, triggersOnly );
}
///
/// Add a shape from a physics hull
///
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;
}
///
/// Add a shape from a mesh hull
///
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 OnIntersectionStart { get; set; }
public Action OnIntersectionUpdate { get; set; }
public Action OnIntersectionEnd { get; set; }
internal Action OnTriggerBegin { get; set; }
internal Action OnTriggerEnd { get; set; }
///
/// Transform, on previous step
///
Transform prevStepTransform;
float prevStepTime;
///
/// Transform on current step
///
Transform stepTransform;
float stepTime;
///
/// Called on each active body after a "step"
///
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();
}
///
/// 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.
///
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 );
}
///
/// 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.
///
[ActionGraphInclude]
public void SmoothMove( in Vector3 position, float timeToArrive, float timeDelta )
{
var velocity = Velocity;
Vector3.SmoothDamp( Position, position, ref velocity, timeToArrive, timeDelta );
Velocity = velocity;
}
///
/// 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.
///
[ActionGraphInclude]
public void SmoothMove( in Transform transform, float smoothTime, float timeDelta )
{
SmoothMove( transform.Position, smoothTime, timeDelta );
SmoothRotate( transform.Rotation, smoothTime, timeDelta );
}
///
/// Rotate the body to this position in a way that cooperates with the physics system.
///
[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();
}
///
/// Called when anything significant changed about this physics object. Like its position,
/// or its enabled status.
///
internal Action OnDirty;
internal HashSet Joints = new HashSet();
internal void AddJoint( Joint joint )
{
Joints.Add( joint );
}
internal void RemoveJoint( Joint joint )
{
Joints.Remove( joint );
}
}