WorkInProgress/PhysicsGrabber.cs

using Sandbox.Physics;

public class PhysicsGraber : Component
{
	private PhysicsBody GrabbedBody;
	private GameObject GrabbedObject;

	private Vector3 GrabbedAimLocal;
	private Vector3 GrabbedObjectLocal;

	private PhysicsBody GrabBody;
	private Sandbox.Physics.FixedJoint GrabJoint;

	protected override void OnEnabled()
	{
		base.OnEnabled();

		Clear();

		GrabBody = new PhysicsBody( Scene.PhysicsWorld )
		{
			BodyType = PhysicsBodyType.Keyframed
		};
	}

	protected override void OnDisabled()
	{
		base.OnDisabled();

		Clear();
	}

	protected override void OnDestroy()
	{
		base.OnDestroy();

		Clear();
	}

	private void Clear()
	{
		GrabJoint?.Remove();
		GrabJoint = null;

		GrabBody?.Remove();
		GrabBody = null;

		GrabbedBody = null;
		GrabbedObject = null;
		GrabbedAimLocal = default;
	}

	protected override void OnUpdate()
	{
		if ( IsProxy )
			return;

		if ( GrabbedBody.IsValid() )
		{
			if ( !Input.Down( "attack1" ) )
			{
				GrabJoint?.Remove();
				GrabJoint = null;

				GrabbedBody = null;
				GrabbedObject = null;
				GrabbedAimLocal = default;
			}
			else
			{
				return;
			}
		}

		var tr = Scene.Trace.Ray( Scene.Camera.WorldPosition, Scene.Camera.WorldPosition + Scene.Camera.WorldRotation.Forward * 1000 )
			.IgnoreGameObjectHierarchy( GameObject.Root )
			.Run();

		if ( !tr.Hit || tr.Body is null )
			return;

		if ( tr.Body.BodyType == PhysicsBodyType.Static )
			return;

		if ( Input.Down( "attack1" ) )
		{
			var aimTransform = Scene.Camera.WorldTransform;

			GrabbedBody = tr.Body;
			GrabbedObject = tr.GameObject;
			GrabbedObjectLocal = GrabbedObject.WorldTransform.PointToLocal( tr.HitPosition );

			var localOffset = GrabbedBody.Transform.PointToLocal( tr.HitPosition );

			GrabbedAimLocal = aimTransform.PointToLocal( tr.HitPosition );
			GrabBody.Position = tr.HitPosition;

			GrabJoint?.Remove();
			GrabJoint = PhysicsJoint.CreateFixed( new PhysicsPoint( GrabBody ), new PhysicsPoint( GrabbedBody ) );
			GrabJoint.Point1 = new PhysicsPoint( GrabBody );
			GrabJoint.Point2 = new PhysicsPoint( GrabbedBody, localOffset );

			var maxForce = 100.0f * tr.Body.Mass * Scene.PhysicsWorld.Gravity.Length;
			GrabJoint.SpringLinear = new PhysicsSpring( 15, 1, maxForce );
			GrabJoint.SpringAngular = new PhysicsSpring( 0, 0, 0 );
		}
	}

	protected override void OnFixedUpdate()
	{
		if ( IsProxy )
			return;

		if ( !GrabbedBody.IsValid() )
			return;

		if ( !GrabBody.IsValid() )
			return;

		var aimTransform = Scene.Camera.WorldTransform;
		GrabBody.Position = aimTransform.PointToWorld( GrabbedAimLocal );
	}

	protected override void OnPreRender()
	{
		base.OnPreRender();

		if ( !GrabbedObject.IsValid() )
		{
			var tr = Scene.Trace.Ray( Scene.Camera.ScreenNormalToRay( 0.5f ), 1000.0f )
						.IgnoreGameObjectHierarchy( GameObject.Root )
						.Run();

			if ( tr.Hit )
			{
				Gizmo.Draw.Color = Color.Cyan;
				Gizmo.Draw.SolidSphere( tr.HitPosition, 1 );
			}
		}
		else
		{
			var position = GrabbedObject.WorldTransform.PointToWorld( GrabbedObjectLocal );

			Gizmo.Draw.Color = Color.Cyan;
			Gizmo.Draw.SolidSphere( position, 1 );
		}
	}
}