using NativeEngine; using System.Runtime.InteropServices; namespace Sandbox; /// /// Represents a basic, convex shape. A PhysicsBody consists of one or more of these. /// [Expose, ActionGraphIgnore] public sealed partial class PhysicsShape : IHandle { #region IHandle // // A pointer to the actual native object // internal IPhysicsShape native; // // IHandle implementation // void IHandle.HandleInit( IntPtr ptr ) => native = ptr; void IHandle.HandleDestroy() { native = IntPtr.Zero; } bool IHandle.HandleValid() => !native.IsNull; #endregion internal PhysicsShape( HandleCreationData _ ) { Tags = new TagAccessor( this ); } /// /// The physics body we belong to. /// [ActionGraphInclude] public PhysicsBody Body => native.GetBody(); [Obsolete] public Vector3 Scale => 1.0f; internal PhysicsShapeType ShapeType => native.GetType_Native(); internal BBox LocalBounds => native.LocalBounds(); internal BBox BuildBounds() => native.BuildBounds(); /// /// The bone index that this physics shape represents /// internal int BoneIndex { get; set; } = -1; /// /// The collider object that created / owns this shape /// [ActionGraphInclude] public Collider Collider { get; set; } /// /// This is a trigger (!) /// [ActionGraphInclude] public bool IsTrigger { get => native.IsTrigger(); set => native.SetTrigger( value ); } /// /// Set the local velocity of the surface so things can slide along it, like a conveyor belt /// public Vector3 SurfaceVelocity { get => native.GetLocalVelocity(); set => native.SetLocalVelocity( value ); } /// /// Enable contact, trace and touch /// public void EnableAllCollision() { var mask = CollisionFunctionMask.EnableSolidContact | CollisionFunctionMask.EnableTouchEvent; native.AddCollisionFunctionMask( (byte)mask ); } /// /// Disable contact, trace and touch /// public void DisableAllCollision() { var mask = CollisionFunctionMask.EnableSolidContact | CollisionFunctionMask.EnableTouchEvent; native.RemoveCollisionFunctionMask( (byte)mask ); } void SetCollisionFunctionFlag( CollisionFunctionMask flag, bool on ) { if ( on ) { native.AddCollisionFunctionMask( (byte)flag ); } else { native.RemoveCollisionFunctionMask( (byte)flag ); } } bool GetCollisionFunctionFlag( CollisionFunctionMask flag ) => (native.GetCollisionFunctionMask() & (byte)flag) != 0; /// /// Controls whether this shape has solid collisions. /// public bool EnableSolidCollisions { get { return GetCollisionFunctionFlag( CollisionFunctionMask.EnableSolidContact ); } set { SetCollisionFunctionFlag( CollisionFunctionMask.EnableSolidContact, value ); } } /// /// Controls whether this shape can fire touch events for its owning entity. (Entity.StartTouch, Touch and EndTouch) /// public bool EnableTouch { get { return GetCollisionFunctionFlag( CollisionFunctionMask.EnableTouchEvent ); } set { SetCollisionFunctionFlag( CollisionFunctionMask.EnableTouchEvent, value ); } } /// /// Controls whether this shape can fire continuous touch events for its owning entity (i.e. calling Entity.Touch every frame) /// public bool EnableTouchPersists { get { return GetCollisionFunctionFlag( CollisionFunctionMask.EnableTouchPersists ); } set { SetCollisionFunctionFlag( CollisionFunctionMask.EnableTouchPersists, value ); } } /// /// Is this a MeshShape /// public bool IsMeshShape => ShapeType == PhysicsShapeType.SHAPE_MESH; /// /// Is this a HullShape /// public bool IsHullShape => ShapeType == PhysicsShapeType.SHAPE_HULL; /// /// Is this a SphereShape /// public bool IsSphereShape => ShapeType == PhysicsShapeType.SHAPE_SPHERE; /// /// Is this a CapsuleShape /// public bool IsCapsuleShape => ShapeType == PhysicsShapeType.SHAPE_CAPSULE; /// /// Is this a HeightfieldShape /// public bool IsHeightfieldShape => ShapeType == PhysicsShapeType.SHAPE_HEIGHTFIELD; /// /// Get sphere properties if we're a sphere type /// public Sphere Sphere { get { if ( !IsSphereShape ) throw new Exception( "PhysicsShape is not type Sphere" ); return native.AsSphere(); } } /// /// Get capsule properties if we're a capsule type /// public Capsule Capsule { get { if ( !IsCapsuleShape ) throw new Exception( "PhysicsShape is not type Capsule" ); return native.AsCapsule(); } } /// /// Recreate the collision mesh (Only if this physics shape is type Capsule) /// internal void UpdateCapsuleShape( Vector3 center1, Vector3 center2, float radius ) { if ( !IsCapsuleShape && !IsSphereShape ) throw new Exception( "PhysicsShape is not type Capsule" ); native.UpdateCapsuleShape( center1, center2, radius ); Dirty(); } /// /// Recreate the collision mesh (Only if this physics shape is type Hull) /// internal void UpdateBoxShape( Vector3 center, Rotation rotation, Vector3 extents ) { if ( !IsHullShape ) throw new Exception( "PhysicsShape is not type hull" ); native.UpdateBoxShape( center, rotation, extents ); Dirty(); } /// /// Recreate the collision mesh (Only if this physics shape is type Mesh) /// public void UpdateMesh( List vertices, List indices ) { UpdateMesh( CollectionsMarshal.AsSpan( vertices ), CollectionsMarshal.AsSpan( indices ) ); } /// /// Recreate the mesh of the shape (Only if this physics shape is type Mesh) /// public unsafe void UpdateMesh( Span vertices, Span indices ) { if ( ShapeType != PhysicsShapeType.SHAPE_MESH ) throw new Exception( "PhysicsShape is not type Mesh" ); if ( vertices.Length == 0 ) return; if ( indices.Length == 0 ) return; var vertexCount = vertices.Length; foreach ( var i in indices ) { if ( i < 0 || i >= vertexCount ) throw new ArgumentOutOfRangeException( $"Index ({i}) out of range ({vertexCount - 1})" ); } fixed ( Vector3* vertices_ptr = vertices ) fixed ( int* indices_ptr = indices ) { native.UpdateMeshShape( vertices.Length, (IntPtr)vertices_ptr, indices.Length, (IntPtr)indices_ptr ); } Dirty(); } /// /// Recreate the hull of the shape (Only if this physics shape is type Hull) /// public unsafe void UpdateHull( Vector3 position, Rotation rotation, Span points ) { if ( ShapeType != PhysicsShapeType.SHAPE_HULL ) throw new Exception( "PhysicsShape is not type Hull" ); if ( points.Length == 0 ) return; fixed ( Vector3* points_ptr = points ) { native.UpdateHullShape( position, rotation, points.Length, (IntPtr)points_ptr ); } Dirty(); } /// /// Controls physical properties of this shape. /// public string SurfaceMaterial { get => native.GetMaterialName(); set { native.SetMaterialIndex( value ); // Because we're setting the surface on the native side, // we also need to update the cached surface to keep it in sync! _surface = string.IsNullOrWhiteSpace( SurfaceMaterial ) ? null : Surface.FindByName( SurfaceMaterial ); } } Surface _surface; [ActionGraphInclude] public Surface Surface { get { if ( _surface is null && !string.IsNullOrWhiteSpace( SurfaceMaterial ) ) { _surface = Surface.FindByName( SurfaceMaterial ); } return _surface; } set { _surface = value; UpdateSurface(); } } internal void UpdateSurface() { native.SetMaterialIndex( _surface?.ResourceName ); } /// /// Multiple surfaces referenced by mesh or heightfield collision. /// [ActionGraphInclude] public Surface[] Surfaces { set => SetSurfaces( value ); } private unsafe void SetSurfaces( Surface[] surfaces ) { if ( surfaces is null ) return; for ( var i = 0; i < surfaces.Length; i++ ) { var surface = surfaces[i]; native.SetSurfaceIndex( surface is null ? -1 : surface.Index, i ); } } /// /// Remove this shape. After calling this the shape should be considered released and not used again. /// public void Remove() { if ( !native.IsValid ) return; if ( !Body.IsValid() ) return; Body.RemoveShape( this ); } /// /// Triangulate this shape. /// public void Triangulate( out Vector3[] positions, out uint[] indices ) { var arrVectors = CUtlVectorVector.Create( 0, 0 ); var arrIndices = CUtlVectorUInt32.Create( 0, 0 ); native.GetTriangulation( arrVectors, arrIndices ); positions = new Vector3[arrVectors.Count()]; indices = new uint[arrIndices.Count()]; for ( var i = 0; i < positions.Length; ++i ) positions[i] = arrVectors.Element( i ); for ( var i = 0; i < indices.Length; ++i ) indices[i] = arrIndices.Element( i ); arrVectors.DeleteThis(); arrIndices.DeleteThis(); } internal IEnumerable GetOutline() { var arrVectors = CUtlVectorVector.Create( 0, 0 ); native.GetOutline( arrVectors ); var count = arrVectors.Count(); for ( int i = 0; i < count; i += 2 ) { yield return new Line( arrVectors.Element( i ), arrVectors.Element( i + 1 ) ); } arrVectors.DeleteThis(); } /// /// The friction value /// public float Friction { get => native.GetFriction(); set => native.SetFriction( value ); } internal float Elasticity { set => native.SetElasticity( value ); } internal float RollingResistance { set => native.SetRollingResistance( value ); } void Dirty() { OnDirty?.Invoke(); } /// /// Called when anything significant changed about this physics object. Like its position, /// or its enabled status. /// internal Action OnDirty; internal bool IsTouching( PhysicsShape shape, bool triggersOnly ) { if ( !shape.IsValid() ) return false; return native.IsTouching( shape, triggersOnly ); } }