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 ); } }