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