mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-08-04 09:43:37 -04:00
546 lines
14 KiB
C#
546 lines
14 KiB
C#
using NativeEngine;
|
|
|
|
namespace Sandbox;
|
|
|
|
/// <summary>
|
|
/// Describes the physics shapes of a model or standalone vphys resource.
|
|
/// Wraps native <c>CPhysicsData</c> the same way <see cref="Model"/> wraps <c>IModel</c>.
|
|
/// </summary>
|
|
public sealed class PhysicsGroupDescription : Resource
|
|
{
|
|
CPhysicsData _native;
|
|
|
|
public override bool IsValid => _native.IsValid;
|
|
|
|
public string Name { get; internal set; }
|
|
|
|
internal int CollisionAttributeCount => _native.GetCollisionAttributeCount();
|
|
|
|
internal PhysicsGroupDescription( CPhysicsData native )
|
|
{
|
|
if ( native.IsNull ) throw new Exception( "CPhysicsData pointer cannot be null!" );
|
|
|
|
_native = native;
|
|
Name = native.GetResourceName();
|
|
|
|
RegisterWeakResourceId( Name );
|
|
Refresh();
|
|
}
|
|
|
|
internal override void Destroy()
|
|
{
|
|
Dispose();
|
|
|
|
if ( !_native.IsNull )
|
|
{
|
|
var n = _native;
|
|
_native = default;
|
|
|
|
MainThread.Queue( () => n.DestroyStrongHandle() );
|
|
}
|
|
|
|
base.Destroy();
|
|
}
|
|
|
|
internal override void OnReloaded()
|
|
{
|
|
// The native resource has been reloaded in place (e.g. a map recompile
|
|
// rewrote world_physics.vphys) - rebuild our parts from the new data.
|
|
Dispose();
|
|
Refresh();
|
|
|
|
foreach ( var scene in Scene.All )
|
|
{
|
|
using var scope = scene.Push();
|
|
|
|
foreach ( var c in scene.GetAllComponents<IHasPhysicsDescription>() )
|
|
{
|
|
if ( c.Physics != this ) continue;
|
|
|
|
c.OnPhysicsReloaded();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create from a resource-system strong handle (e.g. from <see cref="NativeGlue.Resources.GetPhysics"/>).
|
|
/// Uses <see cref="NativeResourceCache"/> exactly like <see cref="Model.FromNative"/>.
|
|
/// </summary>
|
|
internal static PhysicsGroupDescription FromNative( CPhysicsData native )
|
|
{
|
|
if ( native.IsNull || !native.IsStrongHandleValid() )
|
|
return null;
|
|
|
|
var instanceId = native.GetBindingPtr().ToInt64();
|
|
if ( NativeResourceCache.TryGetValue<PhysicsGroupDescription>( instanceId, out var existing ) )
|
|
{
|
|
native.DestroyStrongHandle();
|
|
return existing;
|
|
}
|
|
|
|
var result = new PhysicsGroupDescription( native );
|
|
NativeResourceCache.Add( instanceId, result );
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load a <see cref="PhysicsGroupDescription"/> from a vphys resource path.
|
|
/// </summary>
|
|
public static PhysicsGroupDescription Load( string path )
|
|
{
|
|
if ( string.IsNullOrWhiteSpace( path ) )
|
|
return null;
|
|
|
|
return FromNative( NativeGlue.Resources.GetPhysics( path ) );
|
|
}
|
|
|
|
internal void Dispose()
|
|
{
|
|
foreach ( var p in _parts )
|
|
{
|
|
p.Dispose();
|
|
}
|
|
|
|
_parts.Clear();
|
|
_joints.Clear();
|
|
_surfaces.Clear();
|
|
_tags.Clear();
|
|
}
|
|
|
|
readonly List<BodyPart> _parts = new();
|
|
readonly List<Joint> _joints = new();
|
|
readonly List<string> _surfaces = new();
|
|
readonly List<List<StringToken>> _tags = new();
|
|
|
|
public IReadOnlyList<BodyPart> Parts => _parts;
|
|
public IReadOnlyList<Joint> Joints => _joints;
|
|
|
|
/// <summary>
|
|
/// Enumerate every <see cref="Surface"/> in this physics description.
|
|
/// </summary>
|
|
public IEnumerable<Surface> Surfaces => Enumerable.Range( 0, _surfaces.Count )
|
|
.Select( x => GetSurface( (uint)x ) );
|
|
|
|
void Refresh()
|
|
{
|
|
var surfaceCount = _native.GetSurfacePropertiesCount();
|
|
for ( int i = 0; i < surfaceCount; i++ )
|
|
{
|
|
var s = _native.GetSurfaceProperties( i );
|
|
_surfaces.Add( s.IsValid ? s.m_name : "default" );
|
|
}
|
|
|
|
var attributeCount = _native.GetCollisionAttributeCount();
|
|
for ( int attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++ )
|
|
{
|
|
var tagCount = _native.GetTagCount( attributeIndex );
|
|
var tags = new List<StringToken>( tagCount );
|
|
|
|
for ( int tagIndex = 0; tagIndex < tagCount; tagIndex++ )
|
|
{
|
|
tags.Add( new StringToken( _native.GetTag( attributeIndex, tagIndex ) ) );
|
|
}
|
|
|
|
_tags.Add( tags );
|
|
}
|
|
|
|
for ( int i = 0; i < _native.GetPartCount(); i++ )
|
|
{
|
|
var tx = _native.GetBoneCount() > 0 ? _native.GetBindPose( i ) : Transform.Zero;
|
|
var boneName = _native.GetBoneCount() > 0 ? _native.GetBoneName( i ) : "";
|
|
|
|
_parts.Add( new BodyPart( this, boneName, _native.GetPart( i ), tx ) );
|
|
}
|
|
|
|
for ( int i = 0; i < _native.GetJointCount(); i++ )
|
|
{
|
|
_joints.Add( new Joint( _native.GetJoint( i ) ) );
|
|
}
|
|
}
|
|
|
|
internal Surface GetSurface( uint index )
|
|
{
|
|
if ( index >= _surfaces.Count )
|
|
return null;
|
|
|
|
var surfaceName = _surfaces[(int)index];
|
|
return Surface.FindByName( surfaceName );
|
|
}
|
|
|
|
internal IReadOnlyList<StringToken> GetTags( int index )
|
|
{
|
|
if ( index >= _tags.Count )
|
|
return null;
|
|
|
|
return _tags[index];
|
|
}
|
|
|
|
public int BoneCount => _native.GetBoneCount();
|
|
|
|
public enum JointType
|
|
{
|
|
Ball,
|
|
Hinge,
|
|
Slider,
|
|
Fixed,
|
|
}
|
|
|
|
public sealed class Joint
|
|
{
|
|
internal VPhysXJoint_t native;
|
|
|
|
public JointType Type { get; internal set; }
|
|
public bool Fixed { get; internal set; }
|
|
|
|
public int Body1 => native.m_nBody1;
|
|
public int Body2 => native.m_nBody2;
|
|
|
|
public Transform Frame1 => native.m_Frame1;
|
|
public Transform Frame2 => native.m_Frame2;
|
|
|
|
public bool EnableCollision => native.m_bEnableCollision;
|
|
|
|
public bool EnableLinearLimit => native.m_bEnableLinearLimit;
|
|
public bool EnableLinearMotor => native.m_bEnableLinearMotor;
|
|
public Vector3 LinearTargetVelocity => native.m_vLinearTargetVelocity;
|
|
public float MaxForce => native.m_flMaxForce;
|
|
public float LinearFrequency => native.m_flLinearFrequency;
|
|
public float LinearDampingRatio => native.m_flLinearDampingRatio;
|
|
public float LinearStrength => native.m_flLinearStrength;
|
|
|
|
public bool EnableSwingLimit => native.m_bEnableSwingLimit;
|
|
public bool EnableTwistLimit => native.m_bEnableTwistLimit;
|
|
public bool EnableAngularMotor => native.m_bEnableAngularMotor;
|
|
public Vector3 AngularTargetVelocity => native.m_vAngularTargetVelocity;
|
|
public float MaxTorque => native.m_flMaxTorque;
|
|
public float AngularFrequency => native.m_flAngularFrequency;
|
|
public float AngularDampingRatio => native.m_flAngularDampingRatio;
|
|
public float AngularStrength => native.m_flAngularStrength;
|
|
|
|
public float LinearMin => native.GetLinearLimitMin();
|
|
public float LinearMax => native.GetLinearLimitMax();
|
|
|
|
public float SwingMin => native.GetSwingLimitMin().RadianToDegree();
|
|
public float SwingMax => native.GetSwingLimitMax().RadianToDegree();
|
|
|
|
public float TwistMin => native.GetTwistLimitMin().RadianToDegree();
|
|
public float TwistMax => native.GetTwistLimitMax().RadianToDegree();
|
|
|
|
internal Joint( VPhysXJoint_t native )
|
|
{
|
|
this.native = native;
|
|
|
|
Fixed = native.m_nFlags == 1;
|
|
|
|
var type = (PhysicsJointType)native.m_nType;
|
|
Type = type switch
|
|
{
|
|
PhysicsJointType.SPHERICAL_JOINT or PhysicsJointType.CONICAL_JOINT or PhysicsJointType.QUAT_ORTHOTWIST_JOINT => JointType.Ball,
|
|
PhysicsJointType.REVOLUTE_JOINT => JointType.Hinge,
|
|
PhysicsJointType.PRISMATIC_JOINT => JointType.Slider,
|
|
PhysicsJointType.WELD_JOINT => JointType.Fixed,
|
|
_ => throw new ArgumentOutOfRangeException( nameof( type ), $"Unhandled joint type: {type}" )
|
|
};
|
|
}
|
|
}
|
|
|
|
public sealed class BodyPart
|
|
{
|
|
private readonly PhysicsGroupDescription parent;
|
|
internal VPhysXBodyPart_t native;
|
|
|
|
public Transform Transform { get; init; }
|
|
|
|
public string BoneName { get; init; }
|
|
|
|
private List<Part> All { get; } = new();
|
|
|
|
public float Mass => native.m_flMass;
|
|
public float LinearDamping => native.m_flLinearDamping;
|
|
public float AngularDamping => native.m_flAngularDamping;
|
|
public bool OverrideMassCenter => native.m_bOverrideMassCenter;
|
|
public Vector3 MassCenterOverride => native.m_vMassCenterOverride;
|
|
public float GravityScale => native.m_flGravityScale;
|
|
|
|
internal BodyPart( PhysicsGroupDescription physicsGroupDescription, string boneName, VPhysXBodyPart_t vPhysXBodyPart_t, Transform transform )
|
|
{
|
|
Transform = transform;
|
|
parent = physicsGroupDescription;
|
|
native = vPhysXBodyPart_t;
|
|
BoneName = boneName;
|
|
|
|
for ( int i = 0; i < native.GetSphereCount(); i++ )
|
|
{
|
|
var p = native.GetSphere( i );
|
|
All.Add( new SpherePart( p, parent.GetSurface( p.m_nSurfacePropertyIndex ) ) );
|
|
}
|
|
|
|
for ( int i = 0; i < native.GetCapsuleCount(); i++ )
|
|
{
|
|
var p = native.GetCapsule( i );
|
|
All.Add( new CapsulePart( p, parent.GetSurface( p.m_nSurfacePropertyIndex ) ) );
|
|
}
|
|
|
|
for ( int i = 0; i < native.GetHullCount(); i++ )
|
|
{
|
|
var p = native.GetHull( i );
|
|
All.Add( new HullPart( p, parent.GetSurface( p.m_nSurfacePropertyIndex ) ) );
|
|
}
|
|
|
|
var meshCount = native.GetMeshCount();
|
|
if ( meshCount > 0 )
|
|
{
|
|
var surfaces = Enumerable.Range( 0, parent._surfaces.Count )
|
|
.Select( x => parent.GetSurface( (uint)x ) ).ToArray();
|
|
|
|
for ( int i = 0; i < meshCount; i++ )
|
|
{
|
|
var p = native.GetMesh( i );
|
|
All.Add( new MeshPart( p, parent.GetSurface( p.m_nSurfacePropertyIndex ), surfaces ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void Dispose()
|
|
{
|
|
foreach ( var s in All )
|
|
{
|
|
s.Dispose();
|
|
}
|
|
}
|
|
|
|
public IReadOnlyList<SpherePart> Spheres => All.OfType<SpherePart>().ToList();
|
|
public IReadOnlyList<CapsulePart> Capsules => All.OfType<CapsulePart>().ToList();
|
|
public IReadOnlyList<HullPart> Hulls => All.OfType<HullPart>().ToList();
|
|
public IReadOnlyList<MeshPart> Meshes => All.OfType<MeshPart>().ToList();
|
|
public IReadOnlyList<Part> Parts => All;
|
|
|
|
public abstract class Part
|
|
{
|
|
public Surface Surface { get; protected set; }
|
|
|
|
internal virtual void Dispose()
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
public class SpherePart : Part
|
|
{
|
|
internal RnSphereDesc_t native;
|
|
|
|
public Sphere Sphere { get; init; }
|
|
|
|
internal SpherePart( RnSphereDesc_t native, Surface surface )
|
|
{
|
|
this.native = native;
|
|
Surface = surface;
|
|
Sphere = native.m_Sphere;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get line outline vertices and edge index pairs for debug rendering.
|
|
/// </summary>
|
|
public void GetOutline( out Vector3[] vertices, out int[] indices )
|
|
{
|
|
var arrVectors = CUtlVectorVector.Create( 0, 0 );
|
|
var arrIndices = CUtlVectorUInt32.Create( 0, 0 );
|
|
native.GetOutline( arrVectors, arrIndices );
|
|
|
|
vertices = new Vector3[arrVectors.Count()];
|
|
indices = new int[arrIndices.Count()];
|
|
|
|
for ( int i = 0; i < vertices.Length; i++ )
|
|
vertices[i] = arrVectors.Element( i );
|
|
|
|
for ( int i = 0; i < indices.Length; i++ )
|
|
indices[i] = (int)arrIndices.Element( i );
|
|
|
|
arrVectors.DeleteThis();
|
|
arrIndices.DeleteThis();
|
|
}
|
|
|
|
internal override void Dispose()
|
|
{
|
|
native = default;
|
|
}
|
|
}
|
|
|
|
|
|
public class CapsulePart : Part
|
|
{
|
|
internal RnCapsuleDesc_t native;
|
|
|
|
public Capsule Capsule { get; init; }
|
|
|
|
internal CapsulePart( RnCapsuleDesc_t native, Surface surface )
|
|
{
|
|
this.native = native;
|
|
Surface = surface;
|
|
Capsule = native.m_Capsule;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get line outline vertices and edge index pairs for debug rendering.
|
|
/// </summary>
|
|
public void GetOutline( out Vector3[] vertices, out int[] indices )
|
|
{
|
|
var arrVectors = CUtlVectorVector.Create( 0, 0 );
|
|
var arrIndices = CUtlVectorUInt32.Create( 0, 0 );
|
|
native.GetOutline( arrVectors, arrIndices );
|
|
|
|
vertices = new Vector3[arrVectors.Count()];
|
|
indices = new int[arrIndices.Count()];
|
|
|
|
for ( int i = 0; i < vertices.Length; i++ )
|
|
vertices[i] = arrVectors.Element( i );
|
|
|
|
for ( int i = 0; i < indices.Length; i++ )
|
|
indices[i] = (int)arrIndices.Element( i );
|
|
|
|
arrVectors.DeleteThis();
|
|
arrIndices.DeleteThis();
|
|
}
|
|
|
|
internal override void Dispose()
|
|
{
|
|
native = default;
|
|
}
|
|
}
|
|
|
|
|
|
public class HullPart : Part
|
|
{
|
|
internal RnHullDesc_t native;
|
|
internal RnHull_t hull;
|
|
|
|
public BBox Bounds { get; init; }
|
|
|
|
internal HullPart( RnHullDesc_t native, Surface surface )
|
|
{
|
|
this.native = native;
|
|
Surface = surface;
|
|
hull = native.GetHull();
|
|
|
|
Bounds = hull.GetBbox();
|
|
}
|
|
|
|
/// <summary>
|
|
/// For debug rendering
|
|
/// </summary>
|
|
public IEnumerable<Line> GetLines()
|
|
{
|
|
for ( int i = 0; i < hull.GetEdgeCount(); i++ )
|
|
{
|
|
hull.GetEdgeVertex( i, out var a, out var b );
|
|
yield return new Line( a, b );
|
|
}
|
|
}
|
|
|
|
public IEnumerable<Vector3> GetPoints()
|
|
{
|
|
for ( int i = 0; i < hull.GetVertexCount(); i++ )
|
|
{
|
|
yield return hull.GetVertex( i );
|
|
}
|
|
}
|
|
|
|
internal override void Dispose()
|
|
{
|
|
native = default;
|
|
hull = default;
|
|
}
|
|
}
|
|
|
|
|
|
public class MeshPart : Part
|
|
{
|
|
internal RnMeshDesc_t native;
|
|
internal RnMesh_t mesh;
|
|
|
|
public BBox Bounds { get; init; }
|
|
|
|
public Surface[] Surfaces { get; protected set; }
|
|
|
|
internal MeshPart( RnMeshDesc_t native, Surface surface, Surface[] surfaces )
|
|
{
|
|
this.native = native;
|
|
Surface = surface;
|
|
mesh = native.GetMesh();
|
|
|
|
if ( mesh.GetMaterialCount() > 0 )
|
|
{
|
|
Surfaces = surfaces;
|
|
}
|
|
|
|
Bounds = mesh.GetBbox();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all vertices in this mesh.
|
|
/// </summary>
|
|
public Vector3[] GetVertices()
|
|
{
|
|
var count = mesh.GetVertexCount();
|
|
var result = new Vector3[count];
|
|
|
|
for ( int i = 0; i < count; i++ )
|
|
result[i] = mesh.GetVertex( i );
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all triangle indices in this mesh. Every 3 consecutive values form a triangle.
|
|
/// </summary>
|
|
public int[] GetIndices()
|
|
{
|
|
var count = mesh.GetTriangleCount();
|
|
var result = new int[count * 3];
|
|
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
mesh.GetTriangleIndices( i, out var a, out var b, out var c );
|
|
result[i * 3 + 0] = a;
|
|
result[i * 3 + 1] = b;
|
|
result[i * 3 + 2] = c;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// For debug rendering
|
|
/// </summary>
|
|
public IEnumerable<Triangle> GetTriangles()
|
|
{
|
|
for ( int i = 0; i < mesh.GetTriangleCount(); i++ )
|
|
{
|
|
mesh.GetTriangle( i, out var a, out var b, out var c );
|
|
yield return new Triangle( a, b, c );
|
|
}
|
|
}
|
|
|
|
internal override void Dispose()
|
|
{
|
|
native = default;
|
|
mesh = default;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal interface IHasPhysicsDescription
|
|
{
|
|
/// <summary>
|
|
/// The <see cref="PhysicsGroupDescription"/> associated with this object.
|
|
/// </summary>
|
|
PhysicsGroupDescription Physics { get; }
|
|
|
|
/// <summary>
|
|
/// Called by the engine when the associated <see cref="PhysicsGroupDescription"/> is
|
|
/// reloaded. Implementing classes should rebuild any physics created from it.
|
|
/// </summary>
|
|
void OnPhysicsReloaded();
|
|
}
|