namespace Sandbox;
partial class ModelPhysics
{
///
/// Represents a attached to a specific bone with a local transform.
///
public readonly record struct Body( Rigidbody Component, int Bone, Transform LocalTransform );
///
/// Represents a between two bodies with local frames for each.
///
public readonly record struct Joint( Sandbox.Joint Component, Body Body1, Body Body2, Transform LocalFrame1, Transform LocalFrame2 );
///
/// Networked list of bodies.
///
[Sync, Property, Hide] public List
Bodies { get; set; } = [];
///
/// Networked list of joints.
///
[Sync, Property, Hide] public List Joints { get; set; } = [];
///
/// Networked transforms.
///
[Sync] private NetworkTransforms BodyTransforms { get; set; } = new();
///
/// Sync visual transforms to physics transforms.
///
void UpdateProxyTransforms()
{
if ( !Renderer.IsValid() )
return;
var so = Renderer.SceneModel;
if ( !so.IsValid() )
return;
Renderer.ClearPhysicsBones();
var world = WorldTransform;
foreach ( var (component, boneIndex, _) in Bodies )
{
if ( !component.IsValid() )
continue;
var body = component.PhysicsBody;
if ( !body.IsValid() )
continue;
// Set transform to lerped physics transform.
var bodyTransform = body.GetLerpedTransform( Time.Now ).WithScale( component.WorldScale );
component.WorldTransform = bodyTransform;
// Bone overrides are in modelspace, strip off our world transform from body world transform.
var boneTransform = world.ToLocal( bodyTransform );
so.SetBoneOverride( boneIndex, boneTransform );
}
}
///
/// Send body transforms.
///
void SetBodyTransforms()
{
for ( var i = 0; i < Bodies.Count; i++ )
{
var component = Bodies[i].Component;
if ( !component.IsValid() )
continue;
// If someone enabled networking on this body gameobject then it doesn't need to be sent.
if ( component.GameObject.NetworkMode == NetworkMode.Object )
continue;
var body = component.PhysicsBody;
if ( !body.IsValid() )
continue;
// Physics has gone to sleep, so it's not moving.
if ( body.Sleeping )
continue;
// Mark transform as changed so it gets networked over.
BodyTransforms.Set( i, body.Transform.WithScale( component.WorldScale ) );
}
}
///
/// Move proxy bodies to networked body transforms.
///
void MoveProxyBodies()
{
var transforms = BodyTransforms.Entries;
foreach ( var (bodyIndex, transform) in transforms )
{
var component = Bodies[bodyIndex].Component;
if ( !component.IsValid() )
continue;
component.WorldScale = transform.Scale;
var body = component.PhysicsBody;
if ( !body.IsValid() )
continue;
// Take a bit longer to reach target to smooth it out a bit.
body.Move( transform, Time.Delta * 2.0f );
}
}
protected override void OnRefresh()
{
if ( !IsProxy )
return;
var transforms = BodyTransforms.Entries;
foreach ( var (bodyIndex, transform) in transforms )
{
var component = Bodies[bodyIndex].Component;
if ( !component.IsValid() )
continue;
// Set transform to initial networked transform so physics get created there.
component.GameObject.Flags = GameObjectFlags.Absolute;
component.WorldTransform = transform;
}
}
}