CitizenSuitFollowThrough.cs
using Sandbox;
using System;
namespace PaintballBaddies;
/// <summary>Bounded clothing inertia using the engine's damped spring.</summary>
public sealed class CitizenSuitFollowThrough : Component
{
private SkinnedModelRenderer body,outfit;
private Vector3 previousPosition,previousVelocity;
private bool initialized;
private Model previousModel;
private float appliedWeight;
private bool hasOverride;
private Vector3.SpringDamped spring=new(){Frequency=6,Damping=.8f};
public float CurrentWeight { get; private set; }
public float PeakWeight { get; private set; }
public bool MorphsReady => outfit.IsValid() && outfit.Model.IsValid() &&
outfit.Model.Morphs.GetIndex("SuitFollowThroughLUp")>=0;
public void Configure(SkinnedModelRenderer skeleton,SkinnedModelRenderer garment)
{ body=skeleton;outfit=garment;initialized=false; }
protected override void OnPreRender()
{
if(!MorphsReady || !body.IsValid() || !body.Enabled || !outfit.Enabled
|| !body.GameObject.Active || !outfit.GameObject.Active
|| !outfit.SceneObject.IsValid() || !body.TryGetBoneTransform("spine_2",out var chest))
{ initialized=false;return; }
float dt=Time.Delta;
if(!float.IsFinite(dt) || dt<=0)return;
var position=chest.Position;
if(!float.IsFinite(position.x) || !float.IsFinite(position.y) || !float.IsFinite(position.z))
{ initialized=false;Apply(0);return; }
if(!initialized || previousModel!=outfit.Model || dt>.1f || (position-previousPosition).Length>32)
{
previousModel=outfit.Model;
initialized=true;previousPosition=position;previousVelocity=Vector3.Zero;
spring.Current=spring.Target=spring.Velocity=Vector3.Zero;
Apply(0);return;
}
var velocity=(position-previousPosition)/dt;
var acceleration=(velocity-previousVelocity)/dt;
previousPosition=position;previousVelocity=velocity;
spring.Target=Vector3.Up*(-acceleration.z/900).Clamp(-1,1);
spring.Update(dt);
Apply(spring.Current.z.Clamp(-1,1));
}
private void Apply(float weight)
{
if(!float.IsFinite(weight))weight=0;
CurrentWeight=weight;PeakWeight=MathF.Max(PeakWeight,MathF.Abs(weight));
// A resting suit needs no override. Avoid repeatedly submitting empty
// morph work, including the first frame of a newly created renderer.
if(MathF.Abs(weight)<.0001f)
{
if(hasOverride)
foreach(var name in new[]{"SuitFollowThroughLUp","SuitFollowThroughRUp","SuitFollowThroughLDown","SuitFollowThroughRDown"})
outfit.Morphs.Clear(name,0);
hasOverride=false;appliedWeight=0;return;
}
if(hasOverride && MathF.Abs(weight-appliedWeight)<.0001f)return;
appliedWeight=weight;hasOverride=true;
var up=MathF.Max(0,weight);var down=MathF.Max(0,-weight);
outfit.Morphs.Set("SuitFollowThroughLUp",up,0);
outfit.Morphs.Set("SuitFollowThroughRUp",up,0);
outfit.Morphs.Set("SuitFollowThroughLDown",down,0);
outfit.Morphs.Set("SuitFollowThroughRDown",down,0);
}
// The owning presentation destroys the garment with this component. Never
// queue fresh GPU morph work while that render hierarchy is being torn down.
protected override void OnDestroy() { body=null;outfit=null; }
}