{"TotalCount":23,"Files":[{"Ident":"bobicolab.bobicosoftbones","Path":"BoBiCo Soft-Physics/Components/BoBiCoSoftBoneChain.cs","FileName":"BoBiCoSoftBoneChain.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"//=========================================================================================================================\n// BoBiCo Soft-bone Chain Component\n//=========================================================================================================================\n\nusing System;\nusing System.Collections.Generic;\n\npublic enum SoftBoneLinkColliderShape\n{\n\t/// \u003Csummary\u003E\n\t/// A simple spherical collider.\n\t/// Best for rounded body parts and lightweight collision detection.\n\t/// \u003C/summary\u003E\n\tSphere,\n\n\t/// \u003Csummary\u003E\n\t/// A capsule-shaped collider.\n\t/// Recommended for most bones and elongated body parts.\n\t/// \u003C/summary\u003E\n\tCapsule,\n\n\t/// \u003Csummary\u003E\n\t/// A box-shaped collider.\n\t/// Useful for broad collision areas and block-like shapes.\n\t/// \u003C/summary\u003E\n\tBox,\n\n\t/// \u003Csummary\u003E\n\t/// A one-sided flat collider.\n\t/// Useful for flat surface areas.\n\t/// \u003C/summary\u003E\n\tFlat,\n}\n\npublic enum SoftBoneCollisionMode\n{\n\t/// \u003Csummary\u003E\n\t/// Disables all collision detection for this chain.\n\t/// \u003C/summary\u003E\n\tNone,\n\n\t/// \u003Csummary\u003E\n\t/// Collides only with body colliders.\n\t/// This is the recommended mode for most setups.\n\t/// \u003C/summary\u003E\n\tBaseCollision,\n\n\t/// \u003Csummary\u003E\n\t/// Collides with body colliders and collider chains from the same model.\n\t/// \u003C/summary\u003E\n\tSelfCollision,\n\n\t/// \u003Csummary\u003E\n\t/// Collides with body colliders and collider chains from all SoftBone-enabled models.\n\t/// \u003C/summary\u003E\n\tFullCollision\n}\n\n[Title( \u0022SoftBone Chain\u0022 )]\n[Category( \u0022BoBiCo\u0022 )]\n[Icon( \u0022linear_scale\u0022 )]\n[HelpUrl( \u0022https://bobico-lab.vercel.app/softbone-docs/bobico-softbones/core-components/softbone-chain\u0022 )]\npublic sealed class BoBiCoSoftBoneChain : Component, Component.ExecuteInEditor\n{\n\t// prevents unstable math when values are extremely close to zero.\n\tprivate const float Epsilon = 0.000008f;\n\n\tprivate GameObject _targetBone;\n\tprivate SoftBoneControlMode _controlMode;\n\tprivate bool _curveDefaultsInitialized;\n\n//=========================================================================================================================\n// Chain Properties Section\n//=========================================================================================================================\n\n\t/// \u003Csummary\u003E\n\t/// Defines the root bone of the chain that\u0027ll be simulated.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 1 ), Title( \u0022Target Bone\u0022 ), Group( \u0022Chain Properties\u0022 )]\n\tpublic GameObject TargetBone\n\t{\n\t\tget =\u003E _targetBone;\n\t\tset\n\t\t{\n\t\t\tif ( _targetBone == value )\n\t\t\t\treturn;\n\n\t\t\t_targetBone = value;\n\t\t\tUpdateOwnerName();\n\t\t\tMarkDirty();\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Rotates the chain in local space.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 2 ), Title( \u0022Rotation Offset\u0022 ), Group( \u0022Chain Properties\u0022 )]\n\tpublic Angles RotationOffset { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Control mode for the SoftBone physics parameters. \n\t/// Slider mode uses a single value for each parameter, \n\t/// Range mode uses a min/max range for each parameter, \n\t/// and Curve mode uses a curve to define the parameter values along the chain.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 3 ), Title( \u0022Control Mode\u0022 ), Group( \u0022Chain Properties\u0022 )]\n\tpublic SoftBoneControlMode ControlMode\n\t{\n\t\tget =\u003E _controlMode;\n\t\tset\n\t\t{\n\t\t\tif ( _controlMode == value ) return;\n\t\t\t_controlMode = value;\n\t\t\tif ( value == SoftBoneControlMode.Curve \u0026\u0026 !_curveDefaultsInitialized ) InitializeCurveDefaults();\n\t\t}\n\t}\n\n\tpublic enum SoftBoneControlMode\n    {\n        Slider,\n        Range,\n        Curve\n    }\n\n\t/// \u003Csummary\u003E\n\t/// When enabled, the root bone itself is also physically simulated\n\t/// (wobbles independently). When disabled, only child bones simulate\n\t/// and the root follows animation exactly.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 4 ), Title( \u0022Simulate Root\u0022 ), Group( \u0022Chain Properties\u0022 )]\n\tpublic bool SimulateRoot { get; set; } = false;\n\n\t/// \u003Csummary\u003E\n\t/// When enabled, only the first X chain bones are simulated.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 5 ), Title( \u0022Limit Chain Length\u0022 ), Group( \u0022Chain Properties\u0022 )]\n\tpublic bool LimitChainLength { get; set; } = false;\n\n\t/// \u003Csummary\u003E\n\t/// Limit the amount of chain bones from tip to root that are simulated when Limit Children is enabled.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 6 ), Title( \u0022Chain Length Limit\u0022 ), Group( \u0022Chain Properties\u0022 ), ShowIf( nameof( LimitChainLength ), true )]\n\tpublic int ChainLimit { get; set; } = 0;\n\n//=========================================================================================================================\n// Physics Settings\n//=========================================================================================================================\n\n\t/// \u003Csummary\u003E\t\n\t/// Controls how strongly the chain returns toward its animated rest pose.\n\t/// Higher values reduce swaying and make the chain settle more quickly.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 8 ), Range( 0, 1 ), Title( \u0022Drag\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Pull { get; set; } = 0.20f;\n\n\t/// \u003Csummary\u003E\t\n\t/// Controls how strongly the chain returns toward its animated rest pose.\n\t/// Higher values reduce swaying and make the chain settle more quickly.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 8 ), Range( 0, 1 ), Title( \u0022Drag\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 PullRange { get; set; } = new( 0.20f, 0.20f );\n\n\t/// \u003Csummary\u003E\t\n\t/// Controls how strongly the chain returns toward its animated rest pose.\n\t/// Higher values reduce swaying and make the chain settle more quickly.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 8 ), Title( \u0022Drag\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve PullCurve { get; set; } = ConstantCurve( 0.20f );\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much spring force is applied to the chain.\n\t/// Higher values create softer, bouncier motion, while lower values produce more stable movement.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 9 ), Range( 0, 1 ), Title( \u0022Spring\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Spring { get; set; } = 0.50f;\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much spring force is applied to the chain.\n\t/// Higher values create softer, bouncier motion, while lower values produce more stable movement.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 9 ), Range( 0, 1 ), Title( \u0022Spring\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 SpringRange { get; set; } = new( 0.50f, 0.50f );\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much spring force is applied to the chain.\n\t/// Higher values create softer, bouncier motion, while lower values produce more stable movement.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 9 ), Title( \u0022Spring\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve SpringCurve { get; set; } = ConstantCurve( 0.50f );\n\n\n\t/// \u003Csummary\u003E\n\t/// Controls how resistant the chain is to bending.\n\t/// Higher values make the chain feel firmer, while lower values allow more flexibility.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 10 ), Range( 0, 1 ), Title( \u0022Stiffness\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Stiffness { get; set; } = 0.30f;\n\n\t/// \u003Csummary\u003E\n\t/// Controls how resistant the chain is to bending.\n\t/// Higher values make the chain feel firmer, while lower values allow more flexibility.\n\t/// \u003C/summary\u003E\t\n\t[Property, Order( 10 ), Range( 0, 1 ), Title( \u0022Stiffness\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 StiffnessRange { get; set; } = new( 0.30f, 0.30f );\n\n\t/// \u003Csummary\u003E\n\t/// Controls how resistant the chain is to bending.\n\t/// Higher values make the chain feel firmer, while lower values allow more flexibility.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 10 ), Title( \u0022Stiffness\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve StiffnessCurve { get; set; } = ConstantCurve( 0.30f );\n\n\t/// \u003Csummary\u003E\n\t/// Applies a constant directional force to the chain.\n\t/// Positive values pull downward, while negative values pull upward.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 11 ), Range( -1, 1 ), Title( \u0022Gravity\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Gravity { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Applies a constant directional force to the chain.\n\t/// Positive values pull downward, while negative values pull upward.Positive values pull the chain down, negative values pull the chain up.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 11 ), Range( 0, 1 ), Title( \u0022Gravity\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 GravityRange { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Applies a constant directional force to the chain.\n\t/// Positive values pull downward, while negative values pull upward.Positive values pull the chain down, negative values pull the chain up.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 11 ), Title( \u0022Gravity\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve GravityCurve { get; set; } = ConstantCurve( 0f );\n\n\t/// \u003Csummary\u003E\n\t/// Reduces gravity while the chain is near its animated rest pose.\n\t/// Higher values keep idle chains more stable while preserving gravity during motion.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 12 ), Range( 0, 1 ), Title( \u0022Gravity Falloff\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float GravityFalloff { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Reduces gravity while the chain is near its animated rest pose.\n\t/// Higher values keep idle chains more stable while preserving gravity during motion.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 12 ), Range( 0, 1 ), Title( \u0022Gravity Falloff\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 GravityFalloffRange { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Reduces gravity while the chain is near its animated rest pose.\n\t/// Higher values keep idle chains more stable while preserving gravity during motion.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 12 ), Title( \u0022Gravity Falloff\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve GravityFalloffCurve { get; set; } = ConstantCurve( 0f );\n\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much the chain can extend beyond its rest length.\n\t/// Higher values allow longer, softer stretching under force.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 13 ), Range( 0, 2 ), Title( \u0022Stretch\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Stretch { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much the chain can extend beyond its rest length.\n\t/// Higher values allow longer, softer stretching under force.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 13 ), Range( 0, 2 ), Title( \u0022Stretch\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 StretchRange { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much the chain can extend beyond its rest length.\n\t/// Higher values allow longer, softer stretching under force.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 13 ), Title( \u0022Stretch\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve StretchCurve { get; set; } = ConstantCurve( 0f );\n\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much the chain can compress below its rest length.\n\t/// Higher values allow greater compression.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 14 ), Range( 0, 1 ), Title( \u0022Squish\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Squish { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much the chain can compress below its rest length.\n\t/// Higher values allow greater compression.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 14 ), Range( 0, 1 ), Title( \u0022Squish\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 SquishRange { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much the chain can compress below its rest length.\n\t/// Higher values allow greater compression.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 14 ), Title( \u0022Squish\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve SquishCurve { get; set; } = ConstantCurve( 0f );\n\n\t/// \u003Csummary\u003E\n\t/// Reduces movement caused by the character\u0027s own motion.\n\t/// Higher values make the chain feel heavier and less reactive while the character moves.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 15 ), Range( 0, 1 ), Title( \u0022Movement Resistance\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Immobile { get; set; } = 1.00f;\n\n\t/// \u003Csummary\u003E\n\t/// Reduces movement caused by the character\u0027s own motion.\n\t/// Higher values make the chain feel heavier and less reactive while the character moves.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 15 ), Range( 0, 1 ), Title( \u0022Movement Resistance\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 ImmobileRange { get; set; } = new( 1.00f, 1.00f );\n\n\t/// \u003Csummary\u003E\n\t/// Reduces movement caused by the character\u0027s own motion.\n\t/// Higher values make the chain feel heavier and less reactive while the character moves.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 15 ), Title( \u0022Movement Resistance\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve ImmobileCurve { get; set; } = ConstantCurve( 1.00f );\n\n//=========================================================================================================================\n// Limit Settings Section\n//=========================================================================================================================\n\n    /// \u003Csummary\u003E\n    /// Defines how the chain\u0027s movement is constrained.\n    /// Different limit types provide different ways of restricting bone rotation.\n    /// \u003C/summary\u003E\n\t[Property, Order( 16 ), Title( \u0022Limit Type\u0022 ), Group( \u0022Limit Settings\u0022 )]\n\tpublic SoftBoneLimitType LimitType { get; set; } = SoftBoneLimitType.Cone;\n\n    /// \u003Csummary\u003E\n    /// Maximum angle the chain can rotate away from its rest direction.\n    /// Higher values allow greater freedom of movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 17 ), Range( 0, 180 ), Title( \u0022Max Angle\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxAngleSlider ), true )]\n\tpublic float MaxAngle { get; set; } = 95f;\n\n\t/// \u003Csummary\u003E\n    /// Maximum angle the chain can rotate away from its rest direction.\n    /// Higher values allow greater freedom of movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 17 ), Range( 0, 1 ), Title( \u0022Max Angle\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxAngleRange ), true )]\n\tpublic Vector2 MaxAngleRange { get; set; } = new( 95f, 95f );\n\n\t/// \u003Csummary\u003E\n    /// Maximum angle the chain can rotate away from its rest direction.\n    /// Higher values allow greater freedom of movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 17 ), Title( \u0022Max Angle\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxAngleCurve ), true )]\n\tpublic Curve MaxAngleCurve { get; set; } = ConstantCurve( 95f / 180f );\n\tpublic bool UsesMaxAngle =\u003E LimitType == SoftBoneLimitType.Cone || LimitType == SoftBoneLimitType.Plane;\n\tpublic bool ShowMaxAngleSlider =\u003E UsesMaxAngle \u0026\u0026 ControlMode == SoftBoneControlMode.Slider;\n\tpublic bool ShowMaxAngleRange =\u003E UsesMaxAngle \u0026\u0026 ControlMode == SoftBoneControlMode.Range;\n\tpublic bool ShowMaxAngleCurve =\u003E UsesMaxAngle \u0026\u0026 ControlMode == SoftBoneControlMode.Curve;\n\n    /// \u003Csummary\u003E\n    /// Maximum horizontal rotation allowed for Ellipse limits.\n    /// Increase to allow more side-to-side movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 18 ), Range( 0, 180 ), Title( \u0022Max Yaw\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxYawSlider ), true )]\n\tpublic float MaxYaw { get; set; } = 95f;\n\n\t/// \u003Csummary\u003E\n    /// Maximum horizontal rotation allowed for Ellipse limits.\n    /// Increase to allow more side-to-side movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 18 ), Range( 0, 180 ), Title( \u0022Max Yaw\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxYawRange ), true )]\n\tpublic Vector2 MaxYawRange { get; set; } = new( 95f, 95f );\n\n    /// \u003Csummary\u003E\n    /// Maximum horizontal rotation allowed for Ellipse limits.\n    /// Increase to allow more side-to-side movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 18 ), Title( \u0022Max Yaw\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxYawCurve ), true )]\n\tpublic Curve MaxYawCurve { get; set; } = ConstantCurve( 95f / 180f );\n\tpublic bool ShowMaxYawSlider =\u003E LimitType == SoftBoneLimitType.Ellipse \u0026\u0026 ControlMode == SoftBoneControlMode.Slider;\n\tpublic bool ShowMaxYawRange =\u003E LimitType == SoftBoneLimitType.Ellipse \u0026\u0026 ControlMode == SoftBoneControlMode.Range;\n\tpublic bool ShowMaxYawCurve =\u003E LimitType == SoftBoneLimitType.Ellipse \u0026\u0026 ControlMode == SoftBoneControlMode.Curve;\n\n\n    /// \u003Csummary\u003E\n    /// Maximum vertical rotation allowed for Ellipse limits.\n    /// Increase to allow more up-and-down movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 19 ), Range( 0, 180 ), Title( \u0022Max Pitch\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxPitchSlider ), true )]\n\tpublic float MaxPitch { get; set; } = 95f;\n\n    /// \u003Csummary\u003E\n    /// Maximum vertical rotation allowed for Ellipse limits.\n    /// Increase to allow more up-and-down movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 19 ), Range( 0, 180 ), Title( \u0022Max Pitch\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxPitchRange ), true )]\n\tpublic Vector2 MaxPitchRange { get; set; } = new( 95f, 95f );\n\n    /// \u003Csummary\u003E\n    /// Maximum vertical rotation allowed for Ellipse limits.\n    /// Increase to allow more up-and-down movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 19 ), Title( \u0022Max Pitch\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxPitchCurve ), true )]\n\tpublic Curve MaxPitchCurve { get; set; } = ConstantCurve( 95f / 180f );\n\tpublic bool ShowMaxPitchSlider =\u003E LimitType == SoftBoneLimitType.Ellipse \u0026\u0026 ControlMode == SoftBoneControlMode.Slider;\n\tpublic bool ShowMaxPitchRange =\u003E LimitType == SoftBoneLimitType.Ellipse \u0026\u0026 ControlMode == SoftBoneControlMode.Range;\n\tpublic bool ShowMaxPitchCurve =\u003E ControlMode == SoftBoneControlMode.Curve \u0026\u0026 LimitType == SoftBoneLimitType.Ellipse;\n\n\n    /// \u003Csummary\u003E\n    /// Rotates the limit shape relative to the chain.\n    /// \u003C/summary\u003E\n\t[Property, Order( 20 ), Title( \u0022Limit Rotation\u0022 ), Group( \u0022Limit Settings\u0022 ), HideIf( nameof( LimitType ), SoftBoneLimitType.None )]\n\tpublic Angles LimitRotation { get; set; }\n\n    /// \u003Csummary\u003E\n    /// Shape used for this chain\u0027s collision volume.\n    /// Different shapes provide different collision behavior and coverage.\n    /// \u003C/summary\u003E\n\t[Property, Order( 21 ), Title( \u0022Collider Shape\u0022 ), Group( \u0022Collision Settings\u0022 )]\n\tpublic SoftBoneLinkColliderShape ColliderShape { get; set; } = SoftBoneLinkColliderShape.Capsule;\n\n    /// \u003Csummary\u003E\n    /// Controls which colliders this chain can interact with.\n    /// Higher modes enable more realistic but more expensive collision behavior.\n    /// \u003C/summary\u003E\n\t[Property, Order( 22 ), Title( \u0022Collision Mode\u0022 ), Group( \u0022Collision Settings\u0022 )]\n\tpublic SoftBoneCollisionMode CollisionMode { get; set; } = SoftBoneCollisionMode.BaseCollision;\n\n\t/// \u003Csummary\u003E\n\t/// Assign one or more SoftBone Collider Groups for this chain to interact with.\n\t/// Colliders outside of these groups will be ignored.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 23 ), Title( \u0022Collider Groups\u0022 ), Group( \u0022Collision Settings\u0022 )]\n\tpublic List\u003CGameObject\u003E ColliderGroups { get; set; } = new();\n\n    /// \u003Csummary\u003E\n    /// Allows the colliders to collide with the root collider.\n    /// Disable if the root should remain free.\n    /// \u003C/summary\u003E\n\t[Property, Order( 24 ), Title( \u0022Collide With Root\u0022 ), Group( \u0022Collision Settings\u0022 )]\n\tpublic bool CollideWithRoot { get; set; }\n\n\t// Helpers for conditional property visibility\n\tprivate bool ShowRadiusSlider =\u003E ControlMode == SoftBoneControlMode.Slider \u0026\u0026 ColliderShape != SoftBoneLinkColliderShape.Box;\n\tprivate bool ShowRadiusRange  =\u003E ControlMode == SoftBoneControlMode.Range  \u0026\u0026 ColliderShape != SoftBoneLinkColliderShape.Box;\n\tprivate bool ShowRadiusCurve  =\u003E ControlMode == SoftBoneControlMode.Curve  \u0026\u0026 ColliderShape != SoftBoneLinkColliderShape.Box;\n\n    /// \u003Csummary\u003E\n    /// Radius of the chain\u0027s collision volume.\n    /// Larger values create thicker collision boundaries.\n    /// \u003C/summary\u003E\n\t[Property, Order( 25 ), Title( \u0022Collider Radius\u0022 ), Group( \u0022Collision Settings\u0022 ), ShowIf( nameof( ShowRadiusSlider ), true )]\n\tpublic float ColliderRadius { get; set; } = 0.5f;\n\n    /// \u003Csummary\u003E\n    /// Radius of the chain\u0027s collision volume.\n    /// Larger values create thicker collision boundaries.\n    /// \u003C/summary\u003E\n\t[Property, Order( 25 ), Title( \u0022Collider Radius\u0022 ), Group( \u0022Collision Settings\u0022 ), ShowIf( nameof( ShowRadiusRange ), true )]\n\tpublic Vector2 ColliderRadiusRange { get; set; } = new( 0.5f, 0.5f );\n\n    /// \u003Csummary\u003E\n    /// Radius of the chain\u0027s collision volume.\n    /// Larger values create thicker collision boundaries.\n    /// \u003C/summary\u003E\n\t[Property, Order( 25 ), Title( \u0022Collider Radius\u0022 ), Group( \u0022Collision Settings\u0022 ), ShowIf( nameof( ShowRadiusCurve ), true )]\n\tpublic Curve ColliderRadiusCurve { get; set; } = ConstantCurve( 0.5f );\n\n    /// \u003Csummary\u003E\n    /// Height of Capsule collider.\n\t/// Available only when the collider shape is set to Capsule.\n    /// \u003C/summary\u003E\n\t[Property, Order( 26 ), Title( \u0022Collider Height\u0022 ), Group( \u0022Collision Settings\u0022 ), ShowIf( nameof( ColliderShape ), SoftBoneLinkColliderShape.Capsule )]\n\tpublic float ColliderHeight { get; set; } = 1.25f;\n\n    /// \u003Csummary\u003E\n    /// Defines the dimensions of the box collider.\n    /// Available only when the collider shape is set to Box.\n    /// \u003C/summary\u003E\n\t[Property, Order( 27 ), Title( \u0022Box Dimensions\u0022 ), Group( \u0022Collision Settings\u0022 ), ShowIf( nameof( ColliderShape ), SoftBoneLinkColliderShape.Box )]\n\tpublic Vector3 BoxDimension { get; set; } = new Vector3( 1.25f, 1.25f, 1.25f );\n\n//=========================================================================================================================\n// Gizmo Setting Section\n//=========================================================================================================================\n\n    /// \u003Csummary\u003E\n    /// Displays the chain\u0027s angle limit in the Scene view.\n    /// Useful for visualizing and adjusting movement constraints.\n    /// \u003C/summary\u003E\n\t[Property, Order( 28 ), Header( \u0022Limit Gizmo\u0022 ), Title( \u0022Limit Gizmo\u0022 ), Group( \u0022Gizmo Settings\u0022 )]\n\tpublic bool ShowLimitGizmo { get; set; } = true;\n\n    /// \u003Csummary\u003E\n    /// Keeps the limit gizmo visible even when the chain is not selected.\n    /// Useful for debugging multiple chains at once.\n    /// \u003C/summary\u003E\n\t[Property, Order( 29 ), Title( \u0022Always Show Gizmo\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowLimitGizmo ), true )]\n\tpublic bool AlwaysShowLimitGizmo { get; set; }\n\n    /// \u003Csummary\u003E\n    /// Color used for rendering the limit gizmo.\n    /// \u003C/summary\u003E\n\t[Property, Order( 30 ), Title( \u0022Limit Gizmo Color\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowLimitGizmo ), true )]\n\tpublic Color LimitGizmoColor { get; set; } = Color.Orange;\n\n    /// \u003Csummary\u003E\n    /// Controls the transparency of the limit gizmo.\n    /// \u003C/summary\u003E\n\t[Property, Order( 31 ), Range( 0, 1 ), Title( \u0022Gizmo Opacity\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowLimitGizmo ), true )]\n\tpublic float LimitGizmoOpacity { get; set; } = 1f;\n\n    /// \u003Csummary\u003E\n    /// Controls the size of the limit gizmo in the Scene view.\n    /// Adjust if the visualization appears too large or too small.\n    /// \u003C/summary\u003E\n\t[Property, Order( 32 ), Range( 0.1f, 1f ), Title( \u0022Limit Gizmo Size\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowLimitGizmo ), true )]\n\tpublic float LimitGizmoSize { get; set; } = 0.5f;\n\n    /// \u003Csummary\u003E\n    /// Displays the chain\u0027s collider in the Scene view.\n    /// Useful for visualizing collision shapes and sizes.\n    /// \u003C/summary\u003E\n\t[Property, Order( 33 ), Header( \u0022Collider Gizmo\u0022 ), Title( \u0022Collider Gizmo\u0022 ), Group( \u0022Gizmo Settings\u0022 )]\n\tpublic bool ShowColliderGizmo { get; set; } = true;\n\n    /// \u003Csummary\u003E\n    /// Keeps the collider gizmo visible even when the chain is not selected.\n    /// Useful for debugging collision setups.\n    /// \u003C/summary\u003E\n\t[Property, Order( 34 ), Title( \u0022Always Show Gizmo\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowColliderGizmo ), true )]\n\tpublic bool AlwaysShowGizmo { get; set; }\n\n    /// \u003Csummary\u003E\n    /// Color used for rendering the collider gizmo.\n    /// \u003C/summary\u003E\n\t[Property, Order( 35 ), Title( \u0022Collider Gizmo Color\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowColliderGizmo ), true )]\n\tpublic Color ColliderGizmoColor { get; set; } = Gizmo.Colors.Blue;\n\n    /// \u003Csummary\u003E\n    /// Controls the transparency of the collider gizmo.\n    /// \u003C/summary\u003E\n\t[Property, Order( 36 ), Range( 0, 1 ), Title( \u0022Gizmo Opacity\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowColliderGizmo ), true )]\n\tpublic float ColliderGizmoOpacity { get; set; } = 1f;\n\n//=========================================================================================================================\n// Private States\n//=========================================================================================================================\n\n\tprivate readonly List\u003CSoftBoneJointState\u003E _joints = new();\n\tprivate readonly List\u003CBoBiCoSoftBoneCollider\u003E _activeColliders = new();\n\tprivate readonly List\u003CSoftBoneColliderShape\u003E _externalBodyShapes = new();\n\tprivate readonly List\u003CSoftBoneColliderShape\u003E _externalChainShapes = new();\n\tprivate readonly List\u003CSoftBoneChainColliderSnapshot\u003E _publishedChainShapes = new();\n\tprivate readonly HashSet\u003CBoBiCoSoftBoneCollider\u003E _colliderSet = new();\n\tprivate readonly List\u003CTransform\u003E _framePose = new();\n\tprivate readonly List\u003CTransform\u003E _ragdollAnimationPose = new();\n\tprivate bool _dirty = true;\n\tprivate bool _usingPhysicsPose;\n\n\tpublic IReadOnlyList\u003CSoftBoneJointState\u003E Joints =\u003E _joints;\n\n\tpublic bool IsDirty =\u003E _dirty;\n\n\tpublic void MarkDirty() =\u003E _dirty = true;\n\n//=========================================================================================================================\n// Rebuild Section\n//=========================================================================================================================\n\n\tpublic bool Rebuild( SkinnedModelRenderer rendererOverride = null )\n\t{\n\t\tvar renderer = rendererOverride;\n\t\tif ( renderer is null )\n\t\t\treturn false;\n\n\t\tif ( !renderer.IsValid() || renderer.Model?.Bones is null )\n\t\t\treturn false;\n\n\t\tReleaseCurrentChain( renderer );\n\n\t\tvar boneName = \u0022\u0022;\n\t\tif ( TargetBone is not null \u0026\u0026 TargetBone.IsValid() )\n\t\t\tboneName = TargetBone.Name;\n\t\tboneName = boneName?.Trim() ?? \u0022\u0022;\n\n\t\tif ( string.IsNullOrWhiteSpace( boneName ) || !renderer.Model.Bones.HasBone( boneName ) )\n\t\t\treturn false;\n\n\t\tvar current = renderer.Model.Bones.GetBone( boneName );\n\n\t\twhile ( current is not null )\n\t\t{\n\t\t\tif ( !SoftBoneSkinnedModelUtility.TryGetBoneAnimationLocal( renderer, current, out var animated ) )\n\t\t\t\tbreak;\n\n\t\t\tvar poseVector = GetAnimatedBoneVectorLocal( renderer, current, animated );\n\t\t\tvar length = Math.Max( poseVector.Length, Epsilon );\n\t\t\tvar localPoseVector = animated.Rotation.Inverse * poseVector;\n\n\t\t\tvar rendererDownLocal = RendererDownLocal( renderer );\n\t\t\tvar gravityNormalLocal = animated.Rotation.Inverse * rendererDownLocal;\n\n\t\t\tvar joint = new SoftBoneJointState\n\t\t\t{\n\t\t\t\tBone = current,\n\t\t\t\tOriginalLocalGravityNormal = gravityNormalLocal,\n\t\t\t\tLocalPoseVector = localPoseVector,\n\t\t\t\tRestTransformLocal = animated,\n\t\t\t\tLength = length,\n\t\t\t\tPreviousBegin = animated.Position,\n\t\t\t\tPreviousEnd = animated.Position \u002B poseVector,\n\t\t\t\tPreviousAnimatedBegin = animated.Position,\n\t\t\t\tPreviousAnimatedRotation = animated.Rotation,\n\t\t\t\tPreviousSimulationRotation = animated.Rotation,\n\t\t\t\tImmobileEnd = SoftBoneSkinnedModelUtility.ToWorld( renderer, animated.Position \u002B poseVector ),\n\t\t\t\tPreviousEndWorld = SoftBoneSkinnedModelUtility.ToWorld( renderer, animated.Position \u002B poseVector ),\n\t\t\t\tPreviousVector = poseVector,\n\t\t\t\tPreviousVelocity = Vector3.Zero,\n\t\t\t\tHasHistory = false\n\t\t\t};\n\n\t\t\t_joints.Add( joint );\n\t\t\tif ( current.Children.Count == 0 )\n\t\t\t\tbreak;\n\n\t\t\tcurrent = current.Children[0];\n\t\t}\n\n\t\t_dirty = false;\n\t\treturn _joints.Count \u003E 0;\n\t}\n\n\tprivate void ReleaseCurrentChain( SkinnedModelRenderer renderer )\n\t{\n\t\tif ( renderer is not null \u0026\u0026 renderer.IsValid() \u0026\u0026 renderer.SceneModel.IsValid() )\n\t\t{\n\t\t\tforeach ( var joint in _joints )\n\t\t\t{\n\t\t\t\tif ( joint.Bone is not null \u0026\u0026 renderer.TryGetBoneTransformAnimation( joint.Bone, out var animatedWorld ) )\n\t\t\t\t{\n\t\t\t\t\tvar animatedLocal = renderer.WorldTransform.ToLocal( animatedWorld );\n\t\t\t\t\trenderer.SceneModel.SetBoneOverride( joint.Bone.Index, in animatedLocal );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t_joints.Clear();\n\t\t_framePose.Clear();\n\t\t_ragdollAnimationPose.Clear();\n\t\t_publishedChainShapes.Clear();\n\t\tSoftBoneCollisionRegistry.Remove( this );\n\t}\n\n\tprivate void UpdateOwnerName()\n\t{\n\t\tif ( GameObject is null || !GameObject.IsValid() )\n\t\t\treturn;\n\n\t\tvar desiredName = TargetBone is not null \u0026\u0026 TargetBone.IsValid()\n\t\t\t? TargetBone.Name?.Trim()\n\t\t\t: \u0022Empty Chain\u0022;\n\t\tif ( !string.IsNullOrWhiteSpace( desiredName ) \u0026\u0026 GameObject.Name != desiredName )\n\t\t\tGameObject.Name = desiredName;\n\t}\n\n//=========================================================================================================================\n// Simulation Section\n//=========================================================================================================================\n\n\tpublic void Simulate( SkinnedModelRenderer renderer, float frameRatio )\n\t{\n\t\t// Auto-rebuild if dirty or empty \u2014 chain is self-healing so the Group\n\t\t// doesn\u0027t need to orchestrate the Rebuild/Simulate order externally.\n\t\tif ( _dirty || _joints.Count == 0 )\n\t\t{\n\t\t\tif ( !Rebuild( renderer ) )\n\t\t\t\treturn;\n\t\t}\n\n\t\tif ( renderer is null || !renderer.IsValid() || !renderer.SceneModel.IsValid() )\n\t\t\treturn;\n\n\t\tvar flag = frameRatio \u003E= 1f;\n\t\tvar rendererDownLocal = RendererDownLocal( renderer );\n\t\tCollectActiveColliders();\n\t\tvar usePhysicsPose = IsPhysicsDrivingRenderer( renderer );\n\t\tif ( usePhysicsPose != _usingPhysicsPose )\n\t\t{\n\t\t\t_usingPhysicsPose = usePhysicsPose;\n\t\t\tforeach ( var joint in _joints ) joint.HasHistory = false;\n\t\t\tif ( usePhysicsPose \u0026\u0026 !CaptureRagdollAnimationPose( renderer ) )\n\t\t\t\treturn;\n\t\t\tif ( !usePhysicsPose ) _ragdollAnimationPose.Clear();\n\t\t}\n\n\t\tif ( !CaptureFramePose( renderer, usePhysicsPose ) )\n\t\t\treturn;\n\n\t\tvar hasParentEnd = false;\n\t\tvar parentEnd = Vector3.Zero;\n\t\tvar parentAnimatedRotation = Rotation.Identity;\n\t\tvar parentSimulationRotation = Rotation.Identity;\n\n\t\t// LimitChainLength: simulate only the last ChainLimit bones (tip-to-root).\n\t\t// Handled here instead of Rebuild so SimulateRoot logic stays tied to the\n\t\t// ACTUAL chain root (index 0), not whichever bone happens to be first after slicing.\n\t\tvar startIndex = GetSimulationStartIndex();\n\t\tResetExcludedJointsToPose( renderer, startIndex );\n\n\t\tfor ( var i = startIndex; i \u003C _joints.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar joint = _joints[i];\n\t\t\tvar chainPosition = GetChainPosition( i );\n\t\t\tvar pull = EvaluateUnit( Pull, ControlMode, PullRange, PullCurve, chainPosition );\n\t\t\tvar spring = EvaluateUnit( Spring, ControlMode, SpringRange, SpringCurve, chainPosition );\n\t\t\tvar stiffness = EvaluateUnit( Stiffness, ControlMode, StiffnessRange, StiffnessCurve, chainPosition );\n\t\t\tvar immobile = EvaluateUnit( Immobile, ControlMode, ImmobileRange, ImmobileCurve, chainPosition );\n\t\t\tvar stretch = EvaluateNonNegative( Stretch, ControlMode, StretchRange, StretchCurve, chainPosition );\n\t\t\tvar squish = EvaluateNonNegative( Squish, ControlMode, SquishRange, SquishCurve, chainPosition );\n\t\t\tvar gravity = EvaluateSignedUnit( Gravity, ControlMode, GravityRange, GravityCurve, chainPosition );\n\t\t\tvar gravityFalloff = EvaluateUnit( GravityFalloff, ControlMode, GravityFalloffRange, GravityFalloffCurve, chainPosition );\n\t\t\tvar colliderRadius = EvaluateColliderRadius( chainPosition );\n\t\t\tvar maxAngle = EvaluateAngle( MaxAngle, ControlMode, MaxAngleRange, MaxAngleCurve, chainPosition );\n\t\t\tvar maxYaw = EvaluateAngle( MaxYaw, ControlMode, MaxYawRange, MaxYawCurve, chainPosition );\n\t\t\tvar maxPitch = EvaluateAngle( MaxPitch, ControlMode, MaxPitchRange, MaxPitchCurve, chainPosition );\n\t\t\tvar isActualRoot = i == 0;\n\t\t\tvar isSimulationRoot = i == startIndex;\n\t\t\tvar isRootSimulated = isActualRoot \u0026\u0026 SimulateRoot;\n\t\t\tvar animated = _framePose[i];\n\n\t\t\tvar begin = hasParentEnd ? parentEnd : animated.Position;\n\n\t\t\tvar animatedPoseVector = GetFramePoseVector( i, joint, animated );\n\t\t\tvar parentDeflection = isSimulationRoot ? Rotation.Identity : GetParentDeflection( i, parentAnimatedRotation, parentSimulationRotation );\n\t\t\tvar simulationRotation = parentDeflection * animated.Rotation;\n\t\t\tvar poseVector = parentDeflection * animatedPoseVector;\n\t\t\tif ( isSimulationRoot \u0026\u0026 RotationOffset != default )\n\t\t\t{\n\t\t\t\tvar offsetRotation = new Angles( RotationOffset.yaw, RotationOffset.pitch, RotationOffset.roll ).ToRotation();\n\t\t\t\tsimulationRotation = animated.Rotation * offsetRotation;\n\t\t\t\tposeVector = simulationRotation * (animated.Rotation.Inverse * animatedPoseVector);\n\t\t\t}\n\n\t\t\tvar restLength = Math.Max( poseVector.Length, Epsilon );\n\t\t\tvar minLength = Math.Max( 0f, restLength * (1f - squish) );\n\t\t\tvar maxLength = Math.Max( minLength, restLength * (1f \u002B stretch) );\n\n\t\t\tvar endPoint = joint.HasHistory\n\t\t\t\t? renderer.WorldTransform.PointToLocal( joint.PreviousEndWorld )\n\t\t\t\t: begin \u002B poseVector;\n\n\t\t\tif ( joint.HasHistory )\n\t\t\t{\n\t\t\t\tvar rootDelta = begin - joint.PreviousAnimatedBegin;\n\t\t\t\tendPoint \u002B= rootDelta;\n\n\t\t\t\tvar rotationDelta = animated.Rotation * joint.PreviousAnimatedRotation.Inverse;\n\t\t\t\tendPoint = begin \u002B rotationDelta * (endPoint - begin);\n\t\t\t}\n\n\t\t\t// SimulateRoot=false: only skip physics on the TRUE chain root, not on bones that\n\t\t\t// happen to be first because LimitChainLength trimmed the start of the iteration.\n\t\t\tif ( isActualRoot \u0026\u0026 !isRootSimulated \u0026\u0026 !CollideWithRoot )\n\t\t\t{\n\t\t\t\tResetToRestPose( renderer, joint, animated, begin, poseVector, simulationRotation );\n\t\t\t\tparentEnd = joint.PreviousEnd;\n\t\t\t\tparentAnimatedRotation = animated.Rotation;\n\t\t\t\tparentSimulationRotation = joint.PreviousSimulationRotation;\n\t\t\t\thasParentEnd = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ( pull \u003C= Epsilon )\n\t\t\t{\n\t\t\t\tResetToRestPose( renderer, joint, animated, begin, poseVector, simulationRotation );\n\t\t\t\tif ( !isActualRoot )\n\t\t\t\t\tApplySolvedTransform( renderer, joint, animated, simulationRotation );\n\t\t\t\tparentEnd = joint.PreviousEnd;\n\t\t\t\tparentAnimatedRotation = animated.Rotation;\n\t\t\t\tparentSimulationRotation = joint.PreviousSimulationRotation;\n\t\t\t\thasParentEnd = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar previousEndPoint = endPoint;\n\n\t\t\tif ( joint.HasHistory \u0026\u0026 immobile \u003E Epsilon )\n\t\t\t{\n\t\t\t\tvar immobileTarget = renderer.WorldTransform.PointToLocal( joint.ImmobileEnd );\n\t\t\t\tendPoint \u002B= (immobileTarget - endPoint) * immobile;\n\t\t\t}\n\n\t\t\tvar gravityAmount = GetGravityAmount( joint, animated, rendererDownLocal, gravity, gravityFalloff );\n\t\t\tvar gravityTargetEnd = ApplyGravityTarget( begin, poseVector, restLength, gravityAmount, gravity, rendererDownLocal );\n\n\t\t\tvar solvedEndPoint = BoBiCoSoftBoneSolver.IntegrateAdvanced(\n\t\t\t\tbegin, endPoint, previousEndPoint,\n\t\t\t\tposeVector, joint.PreviousVector, joint.PreviousVelocity,\n\t\t\t\tminLength, maxLength, pull, spring, stiffness, gravityTargetEnd, frameRatio,\n\t\t\t\tout var newVelocity );\n\n\t\t\tvar solvedDirection = BoBiCoSoftBoneSolver.SafeNormal( solvedEndPoint - begin, poseVector );\n\t\t\tsolvedDirection = SoftBoneAngleLimit.Apply( poseVector, solvedDirection, LimitType, maxAngle, maxYaw, maxPitch, LimitRotation );\n\t\t\tvar solvedLength = Math.Clamp( (solvedEndPoint - begin).Length, minLength, maxLength );\n\t\t\tvar solvedEnd = begin \u002B solvedDirection * solvedLength;\n\t\t\tsolvedEnd = ResolveCollisions( renderer, begin, solvedEnd, poseVector, minLength, maxLength, colliderRadius, maxAngle, maxYaw, maxPitch, isActualRoot \u0026\u0026 CollideWithRoot, out var wasColliding );\n\t\t\tsolvedDirection = BoBiCoSoftBoneSolver.SafeNormal( solvedEnd - begin, poseVector );\n\t\t\tnewVelocity = solvedEnd - previousEndPoint;\n\t\t\tif ( wasColliding ) newVelocity *= 0.75f;\n\n\t\t\tif ( flag )\n\t\t\t{\n\t\t\t\tjoint.PreviousEndWorld = SoftBoneSkinnedModelUtility.ToWorld( renderer, solvedEnd );\n\t\t\t\tjoint.ImmobileEnd = joint.PreviousEndWorld;\n\t\t\t\tjoint.PreviousVector = solvedEnd - begin;\n\t\t\t\tjoint.PreviousVelocity = newVelocity;\n\t\t\t\tjoint.PreviousAnimatedBegin = begin;\n\t\t\t\tjoint.PreviousAnimatedRotation = animated.Rotation;\n\t\t\t}\n\n\t\t\tjoint.PreviousBegin = begin;\n\t\t\tjoint.PreviousEnd = solvedEnd;\n\t\t\tjoint.PreviousSimulationRotation = BoBiCoSoftBoneSolver.SolveHeadRotation( poseVector, solvedEnd - begin, simulationRotation );\n\t\t\tjoint.HasHistory = true;\n\n\t\t\tif ( !isActualRoot )\n\t\t\t\tApplySolvedTransform( renderer, joint, animated, joint.PreviousSimulationRotation );\n\n\t\t\tparentEnd = solvedEnd;\n\t\t\tparentAnimatedRotation = animated.Rotation;\n\t\t\tparentSimulationRotation = joint.PreviousSimulationRotation;\n\t\t\thasParentEnd = true;\n\t\t}\n\n\t\tPublishCollisionShapes( renderer );\n\t}\n\n\tprivate void CollectActiveColliders()\n\t{\n\t\t_activeColliders.Clear();\n\t\t_colliderSet.Clear();\n\n\t\tif ( ColliderGroups is not null )\n\t\t{\n\t\t\tforeach ( var sourceObject in ColliderGroups )\n\t\t\t{\n\t\t\t\tif ( sourceObject is null || !sourceObject.IsValid() )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tsourceObject.GetComponent\u003CBoBiCoSoftBoneColliderGroup\u003E()?.CollectColliders( _activeColliders, _colliderSet );\n\t\t\t}\n\t\t}\n\n\t}\n\n\tprivate void ResetExcludedJointsToPose( SkinnedModelRenderer renderer, int startIndex )\n\t{\n\t\tfor ( var i = 0; i \u003C startIndex; i\u002B\u002B )\n\t\t{\n\t\t\tvar joint = _joints[i];\n\t\t\tvar animated = _framePose[i];\n\t\t\tvar poseVector = GetFramePoseVector( i, joint, animated );\n\t\t\tResetToRestPose( renderer, joint, animated, animated.Position, poseVector, animated.Rotation );\n\t\t\tif ( i \u003E 0 )\n\t\t\t\tApplyAnimationTransform( renderer, joint, animated );\n\t\t}\n\t}\n\n\tprivate Vector3 ResolveCollisions( SkinnedModelRenderer renderer, Vector3 begin, Vector3 solvedEnd, Vector3 poseVector, float minLength, float maxLength, float colliderRadius, float maxAngle, float maxYaw, float maxPitch, bool includeRootHead, out bool collided )\n\t{\n\t\tcollided = false;\n\t\tif ( CollisionMode == SoftBoneCollisionMode.None || colliderRadius \u003C= Epsilon )\n\t\t\treturn solvedEnd;\n\n\t\tvar worldBegin = SoftBoneSkinnedModelUtility.ToWorld( renderer, begin );\n\t\tvar worldEnd = SoftBoneSkinnedModelUtility.ToWorld( renderer, solvedEnd );\n\t\tforeach ( var collider in _activeColliders )\n\t\t{\n\t\t\tif ( collider.TryGetWorldShape( collider.GetOwningRenderer(), out var shape ) )\n\t\t\t\tcollided |= SoftBoneCollisionSolver.Resolve( ref worldEnd, worldBegin, includeRootHead ? worldBegin : GetCollisionStart( worldBegin, worldEnd, colliderRadius ), colliderRadius, minLength, maxLength, shape );\n\t\t}\n\n\t\tif ( CollisionMode == SoftBoneCollisionMode.FullCollision )\n\t\t{\n\t\t\t_externalBodyShapes.Clear();\n\t\t\tSoftBoneCollisionRegistry.CollectExternalBodyShapes( renderer, _externalBodyShapes );\n\t\t\tforeach ( var shape in _externalBodyShapes )\n\t\t\t\tcollided |= SoftBoneCollisionSolver.Resolve( ref worldEnd, worldBegin, includeRootHead ? worldBegin : GetCollisionStart( worldBegin, worldEnd, colliderRadius ), colliderRadius, minLength, maxLength, shape );\n\t\t}\n\n\t\t_externalChainShapes.Clear();\n\t\tSoftBoneCollisionRegistry.CollectChainShapes( this, renderer, CollisionMode, _externalChainShapes );\n\t\tforeach ( var shape in _externalChainShapes )\n\t\t\tcollided |= SoftBoneCollisionSolver.Resolve( ref worldEnd, worldBegin, includeRootHead ? worldBegin : GetCollisionStart( worldBegin, worldEnd, colliderRadius ), colliderRadius, minLength, maxLength, shape );\n\n\t\tif ( !collided )\n\t\t\treturn solvedEnd;\n\n\t\tvar localDelta = SoftBoneSkinnedModelUtility.ToLocal( renderer, worldEnd ) - begin;\n\t\tvar localDirection = SoftBoneAngleLimit.Apply( poseVector, BoBiCoSoftBoneSolver.SafeNormal( localDelta, poseVector ), LimitType, maxAngle, maxYaw, maxPitch, LimitRotation );\n\t\treturn begin \u002B localDirection * Math.Clamp( localDelta.Length, minLength, maxLength );\n\t}\n\n\tprivate Vector3 GetCollisionStart( Vector3 worldBegin, Vector3 worldEnd, float colliderRadius )\n\t{\n\t\tif ( ColliderShape != SoftBoneLinkColliderShape.Capsule )\n\t\t\treturn worldEnd;\n\n\t\tvar axis = worldEnd - worldBegin;\n\t\tvar segmentLength = Math.Max( 0f, ColliderHeight - colliderRadius * 2f );\n\t\tif ( axis.Length \u003C= Epsilon || segmentLength \u003C= Epsilon )\n\t\t\treturn worldEnd;\n\n\t\treturn worldEnd - axis.Normal * Math.Min( axis.Length, segmentLength );\n\t}\n\n\tprivate int GetSimulationStartIndex()\n\t{\n\t\treturn LimitChainLength \u0026\u0026 ChainLimit \u003E 0 \u0026\u0026 ChainLimit \u003C _joints.Count\n\t\t\t? _joints.Count - ChainLimit\n\t\t\t: 0;\n\t}\n\n\tprivate float GetChainPosition( int index ) =\u003E _joints.Count \u003C= 1 ? 0f : index / (float)(_joints.Count - 1);\n\tprivate static Curve ConstantCurve( float value ) =\u003E new( new Curve.Frame( 0f, value ), new Curve.Frame( 1f, value ) );\n\n\tprivate void InitializeCurveDefaults()\n\t{\n\t\t_curveDefaultsInitialized = true;\n\t\tPullCurve = ConstantCurve( Pull );\n\t\tSpringCurve = ConstantCurve( Spring );\n\t\tStiffnessCurve = ConstantCurve( Stiffness );\n\t\tImmobileCurve = ConstantCurve( Immobile );\n\t\tStretchCurve = ConstantCurve( Stretch );\n\t\tSquishCurve = ConstantCurve( Squish );\n\t\tGravityCurve = ConstantCurve( Gravity );\n\t\tGravityFalloffCurve = ConstantCurve( GravityFalloff );\n\t\tMaxAngleCurve = ConstantCurve( MaxAngle / 180f );\n\t\tMaxYawCurve = ConstantCurve( MaxYaw / 180f );\n\t\tMaxPitchCurve = ConstantCurve( MaxPitch / 180f );\n\t\tColliderRadiusCurve = ConstantCurve( ColliderRadius );\n\t}\n\n\tprivate static float EvaluateUnit( float value, SoftBoneControlMode mode, Vector2 range, Curve curve, float position ) =\u003E Math.Clamp( EvaluateControl( value, mode, range, curve, position ), 0f, 1f );\n\tprivate static float EvaluateSignedUnit( float value, SoftBoneControlMode mode, Vector2 range, Curve curve, float position ) =\u003E Math.Clamp( EvaluateControl( value, mode, range, curve, position ), -1f, 1f );\n\tprivate static float EvaluateNonNegative( float value, SoftBoneControlMode mode, Vector2 range, Curve curve, float position ) =\u003E Math.Max( 0f, EvaluateControl( value, mode, range, curve, position ) );\n\tprivate static float EvaluateAngle( float value, SoftBoneControlMode mode, Vector2 range, Curve curve, float position )\n\t{\n\t\tif ( mode == SoftBoneControlMode.Curve )\n\t\t\treturn Math.Clamp( (curve.Length \u003E 0 ? curve.Evaluate( position ) : 1f) * 180f, 0f, 180f );\n\n\t\treturn Math.Clamp( EvaluateControl( value, mode, range, curve, position ), 0f, 180f );\n\t}\n\tprivate float EvaluateColliderRadius( float position ) =\u003E Math.Max( 0.001f, EvaluateControl( ColliderRadius, ControlMode, ColliderRadiusRange, ColliderRadiusCurve, position ) );\n\tprivate static float EvaluateControl( float value, SoftBoneControlMode mode, Vector2 range, Curve curve, float position )\n\t{\n\t\treturn mode switch\n\t\t{\n\t\t\tSoftBoneControlMode.Range =\u003E range.x \u002B (range.y - range.x) * position,\n\t\t\tSoftBoneControlMode.Curve =\u003E curve.Length \u003E 0 ? curve.Evaluate( position ) : value,\n\t\t\t_ =\u003E value\n\t\t};\n\t}\n\n\tinternal void PublishCollisionShapes( SkinnedModelRenderer renderer )\n\t{\n\t\tif ( CollisionMode == SoftBoneCollisionMode.None )\n\t\t{\n\t\t\tSoftBoneCollisionRegistry.Remove( this );\n\t\t\treturn;\n\t\t}\n\n\t\t_publishedChainShapes.Clear();\n\t\tfor ( var i = GetSimulationStartIndex(); i \u003C _joints.Count; i\u002B\u002B )\n\t\t{\n\t\t\tif ( i == 0 \u0026\u0026 !CollideWithRoot )\n\t\t\t\tcontinue;\n\n\t\t\tvar joint = _joints[i];\n\t\t\tif ( !joint.HasHistory )\n\t\t\t\tcontinue;\n\n\t\t\tvar begin = SoftBoneSkinnedModelUtility.ToWorld( renderer, joint.PreviousBegin );\n\t\t\tvar end = SoftBoneSkinnedModelUtility.ToWorld( renderer, joint.PreviousEnd );\n\t\t\tvar radius = EvaluateColliderRadius( GetChainPosition( i ) );\n\t\t\tvar direction = BoBiCoSoftBoneSolver.SafeNormal( end - begin, Vector3.Forward );\n\t\t\tSoftBoneColliderShape shape;\n\t\t\tswitch ( ColliderShape )\n\t\t\t{\n\t\t\t\tcase SoftBoneLinkColliderShape.Sphere:\n\t\t\t\t\tshape = SoftBoneColliderShape.Sphere( begin, radius );\n\t\t\t\t\tbreak;\n\t\t\t\tcase SoftBoneLinkColliderShape.Box:\n\t\t\t\t\tshape = SoftBoneColliderShape.Box( begin, Rotation.FromToRotation( Vector3.Forward, direction ), new Vector3( BoxDimension.z, BoxDimension.x, BoxDimension.y ) );\n\t\t\t\t\tbreak;\n\t\t\t\tcase SoftBoneLinkColliderShape.Flat:\n\t\t\t\t\tshape = SoftBoneColliderShape.Flat( begin, direction );\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tvar halfLine = Math.Max( 0f, ColliderHeight * 0.5f - radius );\n\t\t\t\t\tshape = SoftBoneColliderShape.Capsule( begin - direction * halfLine, begin \u002B direction * halfLine, radius );\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t_publishedChainShapes.Add( new SoftBoneChainColliderSnapshot( renderer, shape ) );\n\t\t}\n\n\t\tSoftBoneCollisionRegistry.Publish( this, renderer, _publishedChainShapes );\n\t}\n\n\n//=========================================================================================================================\n// Helpers\n//=========================================================================================================================\n\n\tprivate bool CaptureFramePose( SkinnedModelRenderer renderer, bool usePhysicsPose )\n\t{\n\t\t_framePose.Clear();\n\n\t\tTransform rootWorld = default;\n\t\tif ( !renderer.TryGetBoneTransform( _joints[0].Bone, out rootWorld )\n\t\t\t\u0026\u0026 !renderer.TryGetBoneTransformAnimation( _joints[0].Bone, out rootWorld ) )\n\t\t\treturn false;\n\t\tvar rootPose = renderer.WorldTransform.ToLocal( rootWorld );\n\t\t_framePose.Add( rootPose );\n\n\t\tfor ( var i = 1; i \u003C _joints.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar joint = _joints[i];\n\t\t\tTransform animationPose;\n\t\t\tif ( usePhysicsPose )\n\t\t\t{\n\t\t\t\tif ( _ragdollAnimationPose.Count != _joints.Count ) return false;\n\t\t\t\tanimationPose = ApplyRootPoseDelta( _ragdollAnimationPose[i], _ragdollAnimationPose[0], rootPose );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif ( !renderer.TryGetBoneTransformAnimation( joint.Bone, out var worldTransform ) )\n\t\t\t\t\treturn false;\n\t\t\t\tanimationPose = renderer.WorldTransform.ToLocal( worldTransform );\n\t\t\t}\n\n\t\t\t_framePose.Add( animationPose );\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tprivate bool CaptureRagdollAnimationPose( SkinnedModelRenderer renderer )\n\t{\n\t\t_ragdollAnimationPose.Clear();\n\t\tforeach ( var joint in _joints )\n\t\t{\n\t\t\tif ( !renderer.TryGetBoneTransformAnimation( joint.Bone, out var worldTransform ) )\n\t\t\t{\n\t\t\t\t_ragdollAnimationPose.Clear();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t_ragdollAnimationPose.Add( renderer.WorldTransform.ToLocal( worldTransform ) );\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tprivate static Transform ApplyRootPoseDelta( Transform pose, Transform animationRoot, Transform finalRoot )\n\t{\n\t\tvar rotationDelta = finalRoot.Rotation * animationRoot.Rotation.Inverse;\n\t\tvar positionDelta = finalRoot.Position - rotationDelta * animationRoot.Position;\n\t\treturn new Transform( positionDelta \u002B rotationDelta * pose.Position, rotationDelta * pose.Rotation, pose.Scale );\n\t}\n\n\tprivate bool IsPhysicsDrivingRenderer( SkinnedModelRenderer renderer )\n\t{\n\t\tif ( Scene is null ) return false;\n\t\tforeach ( var physics in Scene.GetAllComponents\u003CModelPhysics\u003E() )\n\t\t\tif ( physics is not null \u0026\u0026 physics.Enabled \u0026\u0026 physics.MotionEnabled \u0026\u0026 physics.Renderer == renderer )\n\t\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\tprivate bool TryGetSolveTransformLocal( SkinnedModelRenderer renderer, SoftBoneJointState joint, out Transform transform )\n\t{\n\t\tif ( renderer is not null \u0026\u0026 renderer.IsValid() \u0026\u0026 joint.Bone is not null\n\t\t\t\u0026\u0026 renderer.TryGetBoneTransformAnimation( joint.Bone, out var worldTransform ) )\n\t\t{\n\t\t\ttransform = renderer.WorldTransform.ToLocal( worldTransform );\n\t\t\treturn true;\n\t\t}\n\n\t\ttransform = joint.RestTransformLocal;\n\t\treturn joint.RestTransformLocal.Scale != Vector3.Zero;\n\t}\n\n\tprivate static Vector3 GetAnimatedBoneVectorLocal( SkinnedModelRenderer renderer, BoneCollection.Bone bone, Transform animated )\n\t{\n\t\tif ( bone.Children.Count \u003E 0 \u0026\u0026 SoftBoneSkinnedModelUtility.TryGetBoneAnimationLocal( renderer, bone.Children[0], out var child ) )\n\t\t{\n\t\t\tvar direction = child.Position - animated.Position;\n\t\t\tif ( direction.Length \u003E Epsilon )\n\t\t\t\treturn direction;\n\t\t}\n\n\t\tvar fallback = animated.Rotation * Vector3.Forward;\n\t\treturn fallback.Length \u003E Epsilon ? fallback : Vector3.Forward;\n\t}\n\n\tprivate Vector3 GetFramePoseVector( int index, SoftBoneJointState joint, Transform pose )\n\t{\n\t\tif ( index \u002B 1 \u003C _framePose.Count )\n\t\t{\n\t\t\tvar direction = _framePose[index \u002B 1].Position - pose.Position;\n\t\t\tif ( direction.Length \u003E Epsilon ) return direction;\n\t\t}\n\n\t\treturn GetPoseVectorLocal( joint, pose );\n\t}\n\n\tprivate static Vector3 GetPoseVectorLocal( SoftBoneJointState joint, Transform animated )\n\t{\n\t\tvar poseVector = animated.Rotation * joint.LocalPoseVector;\n\t\tif ( poseVector.Length \u003E Epsilon )\n\t\t\treturn poseVector;\n\n\t\tvar fallback = animated.Rotation * Vector3.Forward;\n\t\treturn fallback.Length \u003E Epsilon ? fallback : Vector3.Forward;\n\t}\n\n\tprivate static Rotation GetParentDeflection( int index, Rotation parentAnimatedRotation, Rotation parentSimulationRotation )\n\t{\n\t\tif ( index \u003C= 0 )\n\t\t\treturn Rotation.Identity;\n\n\t\treturn parentSimulationRotation * parentAnimatedRotation.Inverse;\n\t}\n\n\tprivate float GetGravityAmount( SoftBoneJointState joint, Transform animated, Vector3 rendererDownLocal, float gravity, float gravityFalloff )\n\t{\n\t\tif ( Math.Abs( gravity ) \u003C= Epsilon )\n\t\t\treturn 0f;\n\n\t\tvar gravityAmount = Math.Abs( gravity );\n\t\tif ( gravityFalloff \u003E Epsilon )\n\t\t{\n\t\t\tvar gravityNormal = animated.Rotation * joint.OriginalLocalGravityNormal;\n\t\t\tvar gravityBias = Math.Min( 1f, 1f - Vector3.Dot(\n\t\t\t\tBoBiCoSoftBoneSolver.SafeNormal( gravityNormal, rendererDownLocal ),\n\t\t\t\trendererDownLocal ) );\n\t\t\tgravityAmount *= (1f - gravityFalloff) \u002B gravityFalloff * gravityBias;\n\t\t}\n\n\t\treturn Math.Clamp( gravityAmount, 0f, 1f );\n\t}\n\n\tprivate static Vector3 ApplyGravityTarget( Vector3 begin, Vector3 poseVector, float restLength, float gravityAmount, float gravity, Vector3 rendererDownLocal )\n\t{\n\t\tif ( gravityAmount \u003C= Epsilon )\n\t\t\treturn begin \u002B poseVector;\n\n\t\tvar gravityVector = (gravity \u003E 0f ? rendererDownLocal : -rendererDownLocal) * restLength;\n\t\tvar targetVector = poseVector \u002B (gravityVector - poseVector) * gravityAmount;\n\t\treturn begin \u002B BoBiCoSoftBoneSolver.SafeNormal( targetVector, poseVector ) * restLength;\n\t}\n\n\tprivate static void ResetToRestPose( SkinnedModelRenderer renderer, SoftBoneJointState joint, Transform animated, Vector3 begin, Vector3 poseVector, Rotation simulationRotation )\n\t{\n\t\tvar restEnd = begin \u002B poseVector;\n\t\tjoint.PreviousBegin = begin;\n\t\tjoint.PreviousEnd = restEnd;\n\t\tjoint.PreviousAnimatedBegin = begin;\n\t\tjoint.PreviousAnimatedRotation = animated.Rotation;\n\t\tjoint.PreviousSimulationRotation = simulationRotation;\n\t\tjoint.ImmobileEnd = SoftBoneSkinnedModelUtility.ToWorld( renderer, restEnd );\n\t\tjoint.PreviousEndWorld = joint.ImmobileEnd;\n\t\tjoint.PreviousVector = poseVector;\n\t\tjoint.PreviousVelocity = Vector3.Zero;\n\t\tjoint.HasHistory = true;\n\t}\n\n\tprivate static void ApplySolvedTransform( SkinnedModelRenderer renderer, SoftBoneJointState joint, Transform animated, Rotation solvedRotation )\n\t{\n\t\tvar localTransform = new Transform( joint.PreviousBegin, solvedRotation, animated.Scale );\n\t\trenderer.SceneModel.SetBoneOverride( joint.Bone.Index, in localTransform );\n\t}\n\n\tprivate static void ApplyAnimationTransform( SkinnedModelRenderer renderer, SoftBoneJointState joint, Transform animated )\n\t{\n\t\trenderer.SceneModel.SetBoneOverride( joint.Bone.Index, in animated );\n\t}\n\n\tprivate static Vector3 RendererDownLocal( SkinnedModelRenderer renderer )\n\t{\n\t\tif ( renderer is null || !renderer.IsValid() )\n\t\t\treturn Vector3.Down;\n\n\t\treturn BoBiCoSoftBoneSolver.SafeNormal( renderer.WorldTransform.Rotation.Inverse * Vector3.Down, Vector3.Down );\n\t}\n\n//=========================================================================================================================\n// Building Gizmo \n//=========================================================================================================================\n\n\tprotected override void DrawGizmos()\n\t{\n\t\tbase.DrawGizmos();\n\t\tvar drawCollider = CollisionMode != SoftBoneCollisionMode.None \u0026\u0026 ShowColliderGizmo \u0026\u0026 (AlwaysShowGizmo || Gizmo.IsSelected);\n\t\tvar drawLimit = ShowLimitGizmo \u0026\u0026 (AlwaysShowLimitGizmo || Gizmo.IsSelected) \u0026\u0026 LimitType != SoftBoneLimitType.None;\n\t\tif ( !drawCollider \u0026\u0026 !drawLimit )\n\t\t\treturn;\n\n\t\tvar renderer = FindRendererForGizmo();\n\t\tif ( renderer is null || !renderer.IsValid() )\n\t\t\treturn;\n\n\t\tif ( _dirty || _joints.Count == 0 )\n\t\t\tRebuild( renderer );\n\n\t\tusing ( Transform.DisableProxy() )\n\t\t{\n\t\t\tGizmo.Transform = new Transform( Vector3.Zero, Rotation.Identity, 1f );\n\t\t\tGizmo.Draw.IgnoreDepth = true;\n\t\t\tGizmo.Draw.LineThickness = 1;\n\t\t\tfor ( var i = GetSimulationStartIndex(); i \u003C _joints.Count; i\u002B\u002B )\n\t\t\t{\n\t\t\t\tvar joint = _joints[i];\n\t\t\t\tif ( !TryGetSolveTransformLocal( renderer, joint, out var animated ) )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tvar solvedBegin = joint.HasHistory ? joint.PreviousBegin : animated.Position;\n\t\t\t\tvar solvedEnd = joint.HasHistory ? joint.PreviousEnd : animated.Position \u002B GetPoseVectorLocal( joint, animated );\n\t\t\t\tvar physicsBegin = SoftBoneSkinnedModelUtility.ToWorld( renderer, solvedBegin );\n\t\t\t\tvar boneObject = renderer.GetBoneObject( joint.Bone );\n\t\t\t\tvar begin = boneObject is not null \u0026\u0026 boneObject.IsValid()\n\t\t\t\t\t? boneObject.WorldPosition\n\t\t\t\t\t: physicsBegin;\n\t\t\t\tvar end = SoftBoneSkinnedModelUtility.ToWorld( renderer, solvedEnd );\n\t\t\t\t// Root bone: only draw when SimulateRoot is enabled.\n\t\t\t\t// Non-root bones: skip leaf/endpoint bones (Children.Count == 0) since their\n\t\t\t\t// endpoint is a synthetic forward poseVector, not a real child position.\n\t\t\t\t// The Children.Count gate must NOT apply to the simulated root \u2014 it may be a\n\t\t\t\t// leaf or near-leaf bone and still needs collision when SimulateRoot is on.\n\t\t\t\tvar drawJointCollider = drawCollider\n\t\t\t\t\t\u0026\u0026 (i != 0 || (SimulateRoot \u0026\u0026 CollideWithRoot));\n\t\t\t\tif ( drawJointCollider )\n\t\t\t\t{\n\t\t\t\t\tGizmo.Draw.Color = ColliderGizmoColor.WithAlpha( ColliderGizmoOpacity );\n\t\t\t\t\tDrawLinkColliderGizmo( begin, end, EvaluateColliderRadius( GetChainPosition( i ) ) );\n\t\t\t\t}\n\n\t\t\t\tvar drawJointLimit = drawLimit \u0026\u0026 (i != 0 || SimulateRoot);\n\t\t\t\tif ( drawJointLimit )\n\t\t\t\t{\n\t\t\t\t\tvar solvedVector = solvedEnd - solvedBegin;\n\t\t\t\t\tvar chainPosition = GetChainPosition( i );\n\t\t\t\t\tvar colliderRadius = EvaluateColliderRadius( chainPosition );\n\t\t\t\t\tDrawLimitGizmo( renderer, physicsBegin, solvedVector, Math.Max( joint.Length, colliderRadius * 2f ) * LimitGizmoSize, EvaluateAngle( MaxAngle, ControlMode, MaxAngleRange, MaxAngleCurve, chainPosition ), EvaluateAngle( MaxYaw, ControlMode, MaxYawRange, MaxYawCurve, chainPosition ), EvaluateAngle( MaxPitch, ControlMode, MaxPitchRange, MaxPitchCurve, chainPosition ) );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void DrawLinkColliderGizmo( Vector3 worldStart, Vector3 worldEnd, float colliderRadius )\n\t{\n\t\tif ( ColliderShape == SoftBoneLinkColliderShape.Sphere )\n\t\t{\n\t\t\tGizmo.Draw.LineSphere( new Sphere( worldStart, colliderRadius ) );\n\t\t\treturn;\n\t\t}\n\t\tif ( ColliderShape == SoftBoneLinkColliderShape.Capsule )\n\t\t{\n\t\t\tvar direction = BoBiCoSoftBoneSolver.SafeNormal( worldEnd - worldStart, Vector3.Forward );\n\t\t\tvar halfLine = Math.Max( 0f, ColliderHeight * 0.5f - colliderRadius );\n\t\t\tGizmo.Draw.LineCapsule( new Capsule( worldStart - direction * halfLine, worldStart \u002B direction * halfLine, colliderRadius ) );\n\t\t\treturn;\n\t\t}\n\n\t\tvar forward = BoBiCoSoftBoneSolver.SafeNormal( worldEnd - worldStart, Vector3.Forward );\n\t\tvar right = BoBiCoSoftBoneSolver.SafeNormal( Vector3.Cross( forward, Vector3.Up ), Vector3.Right );\n\t\tvar up = BoBiCoSoftBoneSolver.SafeNormal( Vector3.Cross( right, forward ), Vector3.Up );\n\t\tif ( ColliderShape == SoftBoneLinkColliderShape.Flat )\n\t\t{\n\t\t\tvar half = colliderRadius;\n\t\t\tvar a = worldStart \u002B (right \u002B up) * half; var b = worldStart \u002B (right - up) * half;\n\t\t\tvar c = worldStart \u002B (-right - up) * half; var d = worldStart \u002B (-right \u002B up) * half;\n\t\t\tGizmo.Draw.Line( a, b ); Gizmo.Draw.Line( b, c ); Gizmo.Draw.Line( c, d ); Gizmo.Draw.Line( d, a );\n\t\t\tGizmo.Draw.Line( worldStart, worldStart \u002B forward * Math.Max( colliderRadius, 0.2f ) );\n\t\t\treturn;\n\t\t}\n\n\t\tvar halfSize = BoxDimension * 0.5f;\n\t\tvar corners = new Vector3[8];\n\t\tfor ( var corner = 0; corner \u003C 8; corner\u002B\u002B )\n\t\t\tcorners[corner] = worldStart\n\t\t\t\t\u002B forward * (((corner \u0026 1) == 0 ? -1f : 1f) * halfSize.z)\n\t\t\t\t\u002B right   * (((corner \u0026 2) == 0 ? -1f : 1f) * halfSize.x)\n\t\t\t\t\u002B up      * (((corner \u0026 4) == 0 ? -1f : 1f) * halfSize.y);\n\t\tint[] edges = { 0, 1, 0, 2, 0, 4, 1, 3, 1, 5, 2, 3, 2, 6, 3, 7, 4, 5, 4, 6, 5, 7, 6, 7 };\n\t\tfor ( var edge = 0; edge \u003C edges.Length; edge \u002B= 2 ) Gizmo.Draw.Line( corners[edges[edge]], corners[edges[edge \u002B 1]] );\n\t}\n\n\tprivate void DrawLimitGizmo( SkinnedModelRenderer renderer, Vector3 worldBegin, Vector3 solvedVector, float radius, float maxAngle, float maxYaw, float maxPitch )\n\t{\n\t\tSoftBoneAngleLimit.GetAxes( solvedVector, LimitRotation, out var axisX, out var axisY, out var axisZ );\n\t\taxisX = renderer.WorldTransform.Rotation * axisX;\n\t\taxisY = renderer.WorldTransform.Rotation * axisY;\n\t\taxisZ = renderer.WorldTransform.Rotation * axisZ;\n\t\tvar gizmoRotation = Rotation.FromAxis( axisY, 90f );\n\t\taxisX = gizmoRotation * axisX;\n\t\taxisZ = gizmoRotation * axisZ;\n\t\tGizmo.Draw.Color = LimitGizmoColor.WithAlpha( LimitGizmoOpacity );\n\t\tconst int steps = 24;\n\t\tif ( LimitType == SoftBoneLimitType.Cone )\n\t\t{\n\t\t\tDrawArc( worldBegin, axisY, axisX, maxAngle, radius, steps );\n\t\t\tDrawArc( worldBegin, axisY, axisZ, maxAngle, radius, steps );\n\t\t}\n\t\telse if ( LimitType == SoftBoneLimitType.Plane )\n\t\t{\n\t\t\tDrawArc( worldBegin, axisY, axisZ, maxAngle, radius, steps );\n\t\t\tGizmo.Draw.Line( worldBegin - axisX * radius * 0.45f, worldBegin \u002B axisX * radius * 0.45f );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDrawArc( worldBegin, axisY, axisX, maxYaw, radius, steps );\n\t\t\tDrawArc( worldBegin, axisY, axisZ, maxPitch, radius, steps );\n\t\t}\n\t}\n\n\tprivate static void DrawArc( Vector3 origin, Vector3 forward, Vector3 side, float angle, float radius, int steps )\n\t{\n\t\tvar previous = origin \u002B Rotation.FromAxis( side, -angle ) * forward * radius;\n\t\tfor ( var i = 1; i \u003C= steps; i\u002B\u002B )\n\t\t{\n\t\t\tvar point = origin \u002B Rotation.FromAxis( side, -angle \u002B angle * 2f * i / steps ) * forward * radius;\n\t\t\tGizmo.Draw.Line( previous, point );\n\t\t\tprevious = point;\n\t\t}\n\t}\n\n\tprivate SkinnedModelRenderer FindRendererForGizmo()\n\t{\n\t\tvar targetRenderer = TargetBone?.GetComponentInParent\u003CSkinnedModelRenderer\u003E( true, true );\n\t\tif ( targetRenderer is not null \u0026\u0026 targetRenderer.IsValid() )\n\t\t\treturn targetRenderer;\n\t\treturn null;\n\t}\n\n\tinternal SkinnedModelRenderer GetTargetRenderer() =\u003E FindRendererForGizmo();\n\n\tprotected override void OnValidate()\n\t{\n\t\tUpdateOwnerName();\n\t\tColliderGizmoOpacity = Math.Clamp( ColliderGizmoOpacity, 0f, 1f );\n\t\tLimitGizmoOpacity = Math.Clamp( LimitGizmoOpacity, 0f, 1f );\n\t\tColliderHeight = Math.Max( ColliderHeight, 0.002f );\n\t\tBoxDimension = new Vector3( Math.Max( BoxDimension.x, 0.001f ), Math.Max( BoxDimension.y, 0.001f ), Math.Max( BoxDimension.z, 0.001f ) );\n\t\tLimitGizmoSize = Math.Max( LimitGizmoSize, 0.1f );\n\t\tMarkDirty();\n\t}\n\n\tprotected override void OnDisabled()\n\t{\n\t\tSoftBoneCollisionRegistry.Remove( this );\n\t}\n}"},{"Ident":"bobicolab.bobicosoftbones","Path":"BoBiCo Soft-Physics/SoftBoneAssembly.cs","FileName":"SoftBoneAssembly.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"// I don\u0027t know, I kept these here to avoid random errors.\r\n\r\nglobal using Sandbox;\r\nglobal using System.Collections.Generic;\r\nglobal using System.Linq;"},{"Ident":"bobicolab.bobicosoftbones","Path":"BoBiCo Soft-Physics/SoftBoneSolver.cs","FileName":"SoftBoneSolver.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"using System;\n\n/// \u003Csummary\u003E\n/// Integrates one SoftBone link in renderer-local space.\n/// \u003C/summary\u003E\npublic static class BoBiCoSoftBoneSolver\n{\n\tprivate const float Epsilon = 0.000008f;\n\n\t/// \u003Csummary\u003E\n\t/// One integration step.  All positions/vectors in renderer-local space.\n\t/// Returns the solved end position (renderer-local) and the new velocity.\n\t/// \u003C/summary\u003E\n\tpublic static Vector3 IntegrateAdvanced(\n\t\tVector3 begin,\n\t\tVector3 endPoint,           // current physics end (may have had Immobile applied before call)\n\t\tVector3 previousEndPoint,   // endPoint before Immobile, for velocity storage\n\t\tVector3 poseVector,         // animated rest direction (renderer-local)\n\t\tVector3 previousVector,     // last frame\u0027s (solvedEnd - begin)\n\t\tVector3 previousVelocity,\n\t\tfloat minLength,\n\t\tfloat maxLength,\n\t\tfloat pull,\n\t\tfloat momentum,\n\t\tfloat stiffness,\n\t\tVector3 gravityTargetEnd,   // begin \u002B gravity-blended direction * restLength\n\t\tfloat frameRatio,           // Time.Delta * 60, used for frameLerp\n\t\tout Vector3 newVelocity )\n\t{\n\t\tvar delta = previousVelocity * momentum;\n\t\tdelta \u002B= (gravityTargetEnd - (endPoint \u002B delta)) * pull;\n\n\t\tif ( stiffness \u003E Epsilon )\n\t\t{\n\t\t\tvar nextVector = endPoint \u002B delta - begin;\n\t\t\tdelta \u002B= (previousVector - nextVector) * stiffness;\n\t\t}\n\n\t\tendPoint \u002B= delta;\n\t\tvar boundedVector = endPoint - begin;\n\t\tvar boundedLength = Math.Clamp( boundedVector.Length, minLength, maxLength );\n\t\tendPoint = begin \u002B SafeNormal( boundedVector, poseVector ) * boundedLength;\n\n\t\t// Limit per-step commitment so long frames do not inject a velocity spike.\n\t\tvar frameLerp = Math.Clamp( (1f / Lerp( 1f, frameRatio, pull )) * frameRatio, 0f, 1f );\n\t\tendPoint = Lerp( previousEndPoint, endPoint, frameLerp );\n\n\t\t// velocity = where we actually moved to, not the raw delta\n\t\tnewVelocity = endPoint - previousEndPoint;\n\n\t\treturn endPoint;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Solve the head bone rotation to point toward the simulated tail.\n\t/// Works in renderer-local space.\n\t/// \u003C/summary\u003E\n\tpublic static Rotation SolveHeadRotation(\n\t\tVector3 poseVector,         // animated rest direction (renderer-local)\n\t\tVector3 solvedVector,       // solvedEnd - begin (renderer-local)\n\t\tRotation animatedRotation )\n\t{\n\t\tvar restAxis = SafeNormal( poseVector, Vector3.Forward );\n\t\tvar solvedAxis = SafeNormal( solvedVector, restAxis );\n\t\treturn Rotation.FromToRotation( restAxis, solvedAxis ) * animatedRotation;\n\t}\n\n\t// \u2500\u2500 Helpers (all renderer-local) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\tpublic static Vector3 SafeNormal( Vector3 value, Vector3 fallback )\n\t{\n\t\tif ( value.Length \u003C= Epsilon )\n\t\t\treturn fallback.Length \u003E Epsilon ? fallback.Normal : Vector3.Forward;\n\n\t\treturn value.Normal;\n\t}\n\n\tprivate static Vector3 Lerp( Vector3 a, Vector3 b, float t ) =\u003E a \u002B (b - a) * t;\n\tprivate static float Lerp( float a, float b, float t ) =\u003E a \u002B (b - a) * t;\n}\n"},{"Ident":"bobicolab.bobicosoftbones","Path":"Code/BoBiCo Soft-Physics/SoftBoneAngleLimit.cs","FileName":"SoftBoneAngleLimit.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"using System;\n\npublic enum SoftBoneLimitType \n{ \n\t/// \u003Csummary\u003E\n\t/// No Limits, The chain can move freely without angular limits.\n\t/// \u003C/summary\u003E\n\tNone, \n\n\t/// \u003Csummary\u003E\n\t/// Limits the movement within a cone-shaped angle from the rest direction.\n\t/// \u003C/summary\u003E\n\tCone, \n\n\t/// \u003Csummary\u003E\n\t/// Limits the movement to one side of a plane, preventing the chain from crossing it.\n\t/// \u003C/summary\u003E\n\tPlane, \n\n\t/// \u003Csummary\u003E\n\t/// Limits the movement within an elliptical cone, allowing different horizontal and vertical angle limits.\n\t/// \u003C/summary\u003E\n\tEllipse \n}\n\npublic static class SoftBoneAngleLimit\n{\n\tprivate const float Epsilon = 0.000008f;\n\n\tpublic static Vector3 Apply( Vector3 poseVector, Vector3 solvedDirection, SoftBoneLimitType type, float maxAngle, float maxYaw, float maxPitch, Angles limitRotation )\n\t{\n\t\tvar direction = SafeNormal( solvedDirection, poseVector );\n\t\tif ( type == SoftBoneLimitType.None ) return direction;\n\t\tGetAxes( poseVector, limitRotation, out var axisX, out var axisY, out var axisZ );\n\t\treturn type switch\n\t\t{\n\t\t\tSoftBoneLimitType.Cone =\u003E ClampCone( direction, axisX, axisY, maxAngle ),\n\t\t\tSoftBoneLimitType.Plane =\u003E ClampPlane( direction, axisX, axisY, axisZ, maxAngle ),\n\t\t\tSoftBoneLimitType.Ellipse =\u003E ClampEllipse( direction, axisX, axisY, axisZ, maxYaw, maxPitch ),\n\t\t\t_ =\u003E direction\n\t\t};\n\t}\n\n\tpublic static void GetAxes( Vector3 poseVector, Angles limitRotation, out Vector3 axisX, out Vector3 axisY, out Vector3 axisZ )\n\t{\n\t\tvar frame = Rotation.FromToRotation( Vector3.Up, SafeNormal( poseVector, Vector3.Up ) ) * limitRotation.ToRotation();\n\t\taxisX = SafeNormal( frame * Vector3.Right, Vector3.Right);\n\t\taxisY = SafeNormal( frame * Vector3.Up, Vector3.Up );\n\t\taxisZ = SafeNormal( Vector3.Cross( axisX, axisY ), Vector3.Forward );\n\t}\n\n\tprivate static Vector3 ClampCone( Vector3 direction, Vector3 axisX, Vector3 axisY, float maxAngle )\n\t{\n\t\tif ( maxAngle \u003E= 180f ) return direction;\n\t\tvar angle = MathF.Acos( Math.Clamp( Vector3.Dot( axisY, direction ), -1f, 1f ) ) * 180f / MathF.PI;\n\t\tif ( angle \u003C= maxAngle ) return direction;\n\t\tvar rotationAxis = Vector3.Cross( axisY, direction );\n\t\tif ( rotationAxis.Length \u003C= Epsilon ) rotationAxis = axisX;\n\t\treturn Rotation.FromAxis( SafeNormal( rotationAxis, axisX ), maxAngle ) * axisY;\n\t}\n\n\tprivate static Vector3 ClampPlane( Vector3 direction, Vector3 axisX, Vector3 axisY, Vector3 axisZ, float maxAngle )\n\t{\n\t\tvar planar = direction - axisX * Vector3.Dot( direction, axisX );\n\t\tplanar = SafeNormal( planar, axisY );\n\t\treturn ClampCone( planar, axisZ, axisY, maxAngle );\n\t}\n\n\tprivate static Vector3 ClampEllipse( Vector3 direction, Vector3 axisX, Vector3 axisY, Vector3 axisZ, float maxYaw, float maxPitch )\n\t{\n\t\tvar x = Vector3.Dot( direction, axisZ );\n\t\tvar y = Vector3.Dot( direction, axisX );\n\t\tvar z = Vector3.Dot( direction, axisY );\n\t\tvar yaw = Math.Clamp( MathF.Atan2( y, z ), -maxYaw * MathF.PI / 180f, maxYaw * MathF.PI / 180f );\n\t\tvar pitch = Math.Clamp( MathF.Asin( Math.Clamp( x, -1f, 1f ) ), -maxPitch * MathF.PI / 180f, maxPitch * MathF.PI / 180f );\n\t\tvar horizontal = MathF.Cos( pitch );\n\t\treturn SafeNormal( axisZ * MathF.Sin( pitch ) \u002B axisX * (MathF.Sin( yaw ) * horizontal) \u002B axisY * (MathF.Cos( yaw ) * horizontal), axisY );\n\t}\n\n\tprivate static Vector3 SafeNormal( Vector3 value, Vector3 fallback ) =\u003E value.Length \u003E Epsilon ? value.Normal : fallback.Length \u003E Epsilon ? fallback.Normal : Vector3.Forward;\n}\n"},{"Ident":"bobicolab.bobicosoftbones","Path":"Code/BoBiCo Soft-Physics/SoftBoneSkinnedModelUtility.cs","FileName":"SoftBoneSkinnedModelUtility.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"using System;\n\npublic static class SoftBoneSkinnedModelUtility\n{\n\tprivate const float Epsilon = 0.000008f;\n\n\tpublic static bool TryGetBoneAnimationLocal( SkinnedModelRenderer renderer, BoneCollection.Bone bone, out Transform transform )\n\t{\n\t\ttransform = default;\n\n\t\tif ( renderer is null || !renderer.IsValid() || bone is null )\n\t\t\treturn false;\n\n\t\tif ( renderer.TryGetBoneTransformAnimation( bone, out var worldTransform ) )\n\t\t{\n\t\t\ttransform = renderer.WorldTransform.ToLocal( worldTransform );\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( renderer.TryGetBoneTransform( bone, out worldTransform ) )\n\t\t{\n\t\t\ttransform = renderer.WorldTransform.ToLocal( worldTransform );\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic static Vector3 ToWorld( SkinnedModelRenderer renderer, Vector3 localPosition )\n\t{\n\t\tif ( renderer is null || !renderer.IsValid() )\n\t\t\treturn localPosition;\n\n\t\treturn renderer.WorldTransform.PointToWorld( localPosition );\n\t}\n\n\tpublic static Vector3 ToLocal( SkinnedModelRenderer renderer, Vector3 worldPosition )\n\t{\n\t\tif ( renderer is null || !renderer.IsValid() )\n\t\t\treturn worldPosition;\n\n\t\treturn renderer.WorldTransform.PointToLocal( worldPosition );\n\t}\n\n\tpublic static Vector3 SafeNormal( Vector3 value, Vector3 fallback )\n\t{\n\t\tif ( value.Length \u003C= Epsilon )\n\t\t\treturn fallback.Length \u003E Epsilon ? fallback.Normal : Vector3.Forward;\n\n\t\treturn value.Normal;\n\t}\n}\n"},{"Ident":"bobicolab.bobicosoftbones","Path":"BoBiCo Soft-Physics/SoftBoneAngleLimit.cs","FileName":"SoftBoneAngleLimit.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"using System;\n\npublic enum SoftBoneLimitType \n{ \n\t/// \u003Csummary\u003E\n\t/// No Limits, The chain can move freely without angular limits.\n\t/// \u003C/summary\u003E\n\tNone, \n\n\t/// \u003Csummary\u003E\n\t/// Limits the movement within a cone-shaped angle from the rest direction.\n\t/// \u003C/summary\u003E\n\tCone, \n\n\t/// \u003Csummary\u003E\n\t/// Limits the movement to one side of a plane, preventing the chain from crossing it.\n\t/// \u003C/summary\u003E\n\tPlane, \n\n\t/// \u003Csummary\u003E\n\t/// Limits the movement within an elliptical cone, allowing different horizontal and vertical angle limits.\n\t/// \u003C/summary\u003E\n\tEllipse \n}\n\npublic static class SoftBoneAngleLimit\n{\n\tprivate const float Epsilon = 0.000008f;\n\n\tpublic static Vector3 Apply( Vector3 poseVector, Vector3 solvedDirection, SoftBoneLimitType type, float maxAngle, float maxYaw, float maxPitch, Angles limitRotation )\n\t{\n\t\tvar direction = SafeNormal( solvedDirection, poseVector );\n\t\tif ( type == SoftBoneLimitType.None ) return direction;\n\t\tGetAxes( poseVector, limitRotation, out var axisX, out var axisY, out var axisZ );\n\t\treturn type switch\n\t\t{\n\t\t\tSoftBoneLimitType.Cone =\u003E ClampCone( direction, axisX, axisY, maxAngle ),\n\t\t\tSoftBoneLimitType.Plane =\u003E ClampPlane( direction, axisX, axisY, axisZ, maxAngle ),\n\t\t\tSoftBoneLimitType.Ellipse =\u003E ClampEllipse( direction, axisX, axisY, axisZ, maxYaw, maxPitch ),\n\t\t\t_ =\u003E direction\n\t\t};\n\t}\n\n\tpublic static void GetAxes( Vector3 poseVector, Angles limitRotation, out Vector3 axisX, out Vector3 axisY, out Vector3 axisZ )\n\t{\n\t\tvar frame = Rotation.FromToRotation( Vector3.Up, SafeNormal( poseVector, Vector3.Up ) ) * limitRotation.ToRotation();\n\t\taxisX = SafeNormal( frame * Vector3.Right, Vector3.Right);\n\t\taxisY = SafeNormal( frame * Vector3.Up, Vector3.Up );\n\t\taxisZ = SafeNormal( Vector3.Cross( axisX, axisY ), Vector3.Forward );\n\t}\n\n\tprivate static Vector3 ClampCone( Vector3 direction, Vector3 axisX, Vector3 axisY, float maxAngle )\n\t{\n\t\tif ( maxAngle \u003E= 180f ) return direction;\n\t\tvar angle = MathF.Acos( Math.Clamp( Vector3.Dot( axisY, direction ), -1f, 1f ) ) * 180f / MathF.PI;\n\t\tif ( angle \u003C= maxAngle ) return direction;\n\t\tvar rotationAxis = Vector3.Cross( axisY, direction );\n\t\tif ( rotationAxis.Length \u003C= Epsilon ) rotationAxis = axisX;\n\t\treturn Rotation.FromAxis( SafeNormal( rotationAxis, axisX ), maxAngle ) * axisY;\n\t}\n\n\tprivate static Vector3 ClampPlane( Vector3 direction, Vector3 axisX, Vector3 axisY, Vector3 axisZ, float maxAngle )\n\t{\n\t\tvar planar = direction - axisX * Vector3.Dot( direction, axisX );\n\t\tplanar = SafeNormal( planar, axisY );\n\t\treturn ClampCone( planar, axisZ, axisY, maxAngle );\n\t}\n\n\tprivate static Vector3 ClampEllipse( Vector3 direction, Vector3 axisX, Vector3 axisY, Vector3 axisZ, float maxYaw, float maxPitch )\n\t{\n\t\tvar x = Vector3.Dot( direction, axisZ );\n\t\tvar y = Vector3.Dot( direction, axisX );\n\t\tvar z = Vector3.Dot( direction, axisY );\n\t\tvar yaw = Math.Clamp( MathF.Atan2( y, z ), -maxYaw * MathF.PI / 180f, maxYaw * MathF.PI / 180f );\n\t\tvar pitch = Math.Clamp( MathF.Asin( Math.Clamp( x, -1f, 1f ) ), -maxPitch * MathF.PI / 180f, maxPitch * MathF.PI / 180f );\n\t\tvar horizontal = MathF.Cos( pitch );\n\t\treturn SafeNormal( axisZ * MathF.Sin( pitch ) \u002B axisX * (MathF.Sin( yaw ) * horizontal) \u002B axisY * (MathF.Cos( yaw ) * horizontal), axisY );\n\t}\n\n\tprivate static Vector3 SafeNormal( Vector3 value, Vector3 fallback ) =\u003E value.Length \u003E Epsilon ? value.Normal : fallback.Length \u003E Epsilon ? fallback.Normal : Vector3.Forward;\n}\n"},{"Ident":"bobicolab.bobicosoftbones","Path":"Code/BoBiCo Soft-Physics/SoftBoneJointState.cs","FileName":"SoftBoneJointState.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"using System;\n\n/// \u003Csummary\u003ERuntime state for one simulated SoftBone joint.\u003C/summary\u003E\npublic sealed class SoftBoneJointState\n{\n\tpublic BoneCollection.Bone Bone;\n\n\t// Rest-pose data\n\tpublic float Length;\n\tpublic Vector3 LocalPoseVector;\n\tpublic Transform RestTransformLocal;\n\tpublic Vector3 OriginalLocalGravityNormal;\n\n\t// Per-frame state. Positions are renderer-local unless suffixed with World.\n\tpublic Vector3 PreviousBegin;\n\tpublic Vector3 PreviousEnd;\n\tpublic Vector3 PreviousAnimatedBegin;\n\tpublic Rotation PreviousAnimatedRotation;\n\tpublic Rotation PreviousSimulationRotation;\n\tpublic Vector3 ImmobileEnd;\n\tpublic Vector3 PreviousEndWorld;\n\tpublic Vector3 PreviousVector;\n\tpublic Vector3 PreviousVelocity;\n\tpublic bool HasHistory;\n}\n"},{"Ident":"bobicolab.bobicosoftbones","Path":"BoBiCo Soft-Physics/Components/BoBiCoSoftBoneGroup.cs","FileName":"BoBiCoSoftBoneGroup.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"//=========================================================================================================================\n// BoBiCo Soft-Bone Group Component\n//=========================================================================================================================\n\nusing System;\nusing System.Collections.Generic;\n\n[Title( \u0022SoftBone Group\u0022 )]\n[Category( \u0022BoBiCo\u0022 )]\n[Icon( \u0022account_tree\u0022 )]\n[HelpUrl( \u0022https://bobico-lab.vercel.app/softbone-docs/bobico-softbones/core-components/softbone-group\u0022 )]\npublic sealed class BoBiCoSoftBoneGroup : Component, Component.ExecuteInEditor\n{\n\tprivate const int MaxFixedSteps = 6;\n\n//=========================================================================================================================\n// Group Setup\n//=========================================================================================================================\n\n\t/// \u003Csummary\u003E\n\t/// Assign one or more SoftBone Chains for this group to manage and simulate.\n    /// Chains outside of the group will be ignored.\n\t/// \u003C/summary\u003E\n\t[Property, Title( \u0022SoftBone Chains\u0022 ), Group( \u0022Chains\u0022 )]\n\tpublic List\u003CGameObject\u003E Chains { get; set; } = new();\n\n\t// \u2500\u2500 Settings \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\t/// \u003Csummary\u003E\n\t/// Physics update rate in Hz. \n\t/// Lower values are cheaper and laggy; higher values are smoother.\n\t/// \u003C/summary\u003E\n\t[Property, Range( 15, 240 ), Title( \u0022Update Rate (Hz)\u0022 ), Group( \u0022Group Settings\u0022 )]\n\tpublic float UpdateRate { get; set; } = 120f;\n\n\t/// \u003Csummary\u003E\n\t/// Skip simulation entirely when the main camera is further than this distance.\n\t/// Bones freeze in place until the camera comes back within range.\n\t/// \u003C/summary\u003E\n\t[Property, Title( \u0022Disable In Far Distance\u0022 ), Group( \u0022Group Settings\u0022 )]\n\tpublic bool DisableFarDistance { get; set; } = false;\n\n\t/// \u003Csummary\u003E\n\t/// The distance at which the simulation is disabled.\n\t/// \u003C/summary\u003E\n\t[Property, Title( \u0022Far Distance\u0022 ), Group( \u0022Group Settings\u0022 ), ShowIf( nameof( DisableFarDistance ), true )]\n\tpublic float FarDistance { get; set; } = 1000f;\n\n//=========================================================================================================================\n// Private States \u0026 Functions\n//=========================================================================================================================\n\n\tprivate SkinnedModelRenderer _resolvedRenderer;\n\tprivate IDisposable _updateBonesHook;\n\tprivate bool _dirty = true;\n\tprivate float _accumulatedTime;\n\n\tpublic void MarkDirty() =\u003E _dirty = true;\n\n\tprotected override void OnEnabled()\n\t{\n\t\t_dirty = true;\n\t\t_accumulatedTime = 0f;\n\t\tRegisterUpdateBonesHook();\n\t\tEnsureSetup( true );\n\t}\n\n\tprotected override void OnDisabled()\n\t{\n\t\tSetCollisionCulled( false );\n\t\t_updateBonesHook?.Dispose();\n\t\t_updateBonesHook = null;\n\t\t_resolvedRenderer = null;\n\t\t_accumulatedTime = 0f;\n\t}\n\n\tprotected override void OnValidate()\n\t{\n\t\tChains ??= new List\u003CGameObject\u003E();\n\t\tUpdateRate = Math.Clamp( UpdateRate, 15f, 240f );\n\t\tFarDistance = Math.Max( FarDistance, 0f );\n\t\t_dirty = true;\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tif ( _updateBonesHook is not null || !EnsureSetup() )\n\t\t\treturn;\n\n\t\tRunWithAccumulator();\n\t}\n\n\tprivate void RegisterUpdateBonesHook()\n\t{\n\t\t_updateBonesHook?.Dispose();\n\t\t_updateBonesHook = Scene?.AddHook(\n\t\t\tGameObjectSystem.Stage.UpdateBones,\n\t\t\t1,\n\t\t\t() =\u003E UpdateBones(),\n\t\t\tnameof( BoBiCoSoftBoneGroup ),\n\t\t\t\u0022Solve SoftBone chains after animation\u0022 );\n\t}\n\n\tprivate void UpdateBones()\n\t{\n\t\tif ( !Enabled || !EnsureSetup() )\n\t\t\treturn;\n\n\t\tRunWithAccumulator();\n\t}\n\n\t\n\tprivate void SimulateAll( float frameRatio )\n\t{\n\t\tvar renderer = _resolvedRenderer;\n\t\tif ( renderer is null || !renderer.IsValid() )\n\t\t\treturn;\n\n\t\tforeach ( var chainObject in Chains )\n\t\t{\n\t\t\tif ( chainObject is null || !chainObject.IsValid() )\n\t\t\t\tcontinue;\n\n\t\t\tvar chain = chainObject.GetComponent\u003CBoBiCoSoftBoneChain\u003E();\n\t\t\tif ( chain is null || !chain.Enabled )\n\t\t\t\tcontinue;\n\n\t\t\tif ( _dirty || chain.Joints.Count == 0 || chain.IsDirty )\n\t\t\t\tchain.Rebuild( renderer );\n\n\t\t\tchain.PublishCollisionShapes( renderer );\n\t\t}\n\n\t\tSoftBoneCollisionRegistry.BeginStep( Scene );\n\n\t\tforeach ( var chainObject in Chains )\n\t\t{\n\t\t\tif ( chainObject is null || !chainObject.IsValid() )\n\t\t\t\tcontinue;\n\n\t\t\tvar chain = chainObject.GetComponent\u003CBoBiCoSoftBoneChain\u003E();\n\t\t\tif ( chain is null || !chain.Enabled )\n\t\t\t\tcontinue;\n\n\t\t\tif ( _dirty || chain.Joints.Count == 0 || chain.IsDirty )\n\t\t\t\tchain.Rebuild( renderer );\n\n\t\t\tchain.Simulate( renderer, frameRatio );\n\t\t}\n\n\t\t_dirty = false;\n\t}\n\n\tprivate bool EnsureSetup( bool force = false )\n\t{\n\t\tif ( !force \u0026\u0026 !_dirty \u0026\u0026 _resolvedRenderer is not null \u0026\u0026 _resolvedRenderer.IsValid() )\n\t\t\treturn true;\n\n\t\t_resolvedRenderer = ResolveRenderer();\n\n\t\tif ( _resolvedRenderer is null || !_resolvedRenderer.IsValid() )\n\t\t\treturn false;\n\n\t\tChains ??= new List\u003CGameObject\u003E();\n\n\t\tforeach ( var chainObject in Chains )\n\t\t{\n\t\t\tif ( chainObject is null || !chainObject.IsValid() )\n\t\t\t\tcontinue;\n\n\t\t\tchainObject.GetComponent\u003CBoBiCoSoftBoneChain\u003E()?.Rebuild( _resolvedRenderer );\n\t\t}\n\n\t\t_dirty = false;\n\t\treturn true;\n\t}\n\n\tprivate SkinnedModelRenderer ResolveRenderer()\n\t{\n\t\tChains ??= new List\u003CGameObject\u003E();\n\t\tforeach ( var chainObject in Chains )\n\t\t{\n\t\t\tif ( chainObject is null || !chainObject.IsValid() )\n\t\t\t\tcontinue;\n\n\t\t\tvar renderer = chainObject.GetComponent\u003CBoBiCoSoftBoneChain\u003E()?.GetTargetRenderer();\n\t\t\tif ( renderer is not null \u0026\u0026 renderer.IsValid() )\n\t\t\t\treturn renderer;\n\t\t}\n\n\t\treturn null;\n\t}\n\n\n//=========================================================================================================================\n// Bone \u0026 Collision Culling\n//=========================================================================================================================\n\n\tprivate Vector3? GetMainCameraPosition()\n\t{\n\t\tforeach ( var cam in Scene.GetAllComponents\u003CCameraComponent\u003E() )\n\t\t{\n\t\t\tif ( cam.IsMainCamera )\n\t\t\t\treturn cam.WorldPosition;\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate void RunWithAccumulator()\n\t{\n\t\tvar culled = false;\n\t\tif ( DisableFarDistance )\n\t\t{\n\t\t\tvar camPos = GetMainCameraPosition();\n\t\t\tif ( camPos.HasValue )\n\t\t\t{\n\t\t\t\tvar dist = Vector3.DistanceBetween( WorldPosition, camPos.Value );\n\t\t\t\tculled = dist \u003E FarDistance;\n\t\t\t}\n\t\t}\n\n\t\tSetCollisionCulled( culled );\n\t\tif ( culled )\n\t\t{\n\t\t\t_accumulatedTime = 0f;\n\t\t\treturn;\n\t\t}\n\n\t\tvar rate = Math.Clamp( UpdateRate, 15f, 120f );\n\t\t_accumulatedTime \u002B= Time.Delta;\n\n\t\tvar executions = Math.Min( _accumulatedTime * rate, MaxFixedSteps );\n\t\tvar numSteps = (int)MathF.Ceiling( executions );\n\n\t\tif ( numSteps \u003C= 1 )\n\t\t{\n\t\t\tSimulateAll( executions );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor ( var step = 0; step \u003C numSteps; step\u002B\u002B )\n\t\t\t\tSimulateAll( Math.Min( 1f, executions - step ) );\n\t\t}\n\n\t\tif ( executions \u003E= 1f )\n\t\t\t_accumulatedTime = (executions - MathF.Floor( executions )) / rate;\n\t}\n\n\tprivate void SetCollisionCulled( bool culled )\n\t{\n\t\tSoftBoneCollisionRegistry.SetRendererCulled( _resolvedRenderer, culled );\n\t\tif ( !culled ) return;\n\n\t\tforeach ( var chainObject in Chains )\n\t\t{\n\t\t\tvar chain = chainObject?.GetComponent\u003CBoBiCoSoftBoneChain\u003E();\n\t\t\tif ( chain is not null ) SoftBoneCollisionRegistry.Remove( chain );\n\t\t}\n\t}\n}\n"},{"Ident":"bobicolab.bobicosoftbones","Path":"BoBiCo Soft-Physics/SoftBoneCollisionRegistry.cs","FileName":"SoftBoneCollisionRegistry.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"using System.Collections.Generic;\nusing Sandbox;\nusing System.Linq;\n\n\npublic readonly struct SoftBoneChainColliderSnapshot\n{\n\tpublic readonly SkinnedModelRenderer Renderer;\n\tpublic readonly SoftBoneColliderShape Shape;\n\n\tpublic SoftBoneChainColliderSnapshot( SkinnedModelRenderer renderer, SoftBoneColliderShape shape )\n\t{\n\t\tRenderer = renderer;\n\t\tShape = shape;\n\t}\n}\n\npublic readonly struct SoftBoneBodyColliderSnapshot\n{\n\tpublic readonly SkinnedModelRenderer Renderer;\n\tpublic readonly SoftBoneColliderShape Shape;\n\n\tpublic SoftBoneBodyColliderSnapshot( SkinnedModelRenderer renderer, SoftBoneColliderShape shape )\n\t{\n\t\tRenderer = renderer;\n\t\tShape = shape;\n\t}\n}\n\n/// \u003Csummary\u003EPublishes the previous solved link shapes used by cross-chain contacts.\u003C/summary\u003E\npublic static class SoftBoneCollisionRegistry\n{\n\tprivate static readonly Dictionary\u003CBoBiCoSoftBoneChain, List\u003CSoftBoneChainColliderSnapshot\u003E\u003E PublishedShapes = new();\n\tprivate static readonly Dictionary\u003CBoBiCoSoftBoneChain, List\u003CSoftBoneChainColliderSnapshot\u003E\u003E QueryShapes = new();\n\tprivate static readonly List\u003CSoftBoneBodyColliderSnapshot\u003E QueryBodyShapes = new();\n\tprivate static readonly HashSet\u003CSkinnedModelRenderer\u003E CulledRenderers = new();\n\n\tpublic static void SetRendererCulled( SkinnedModelRenderer renderer, bool culled )\n\t{\n\t\tif ( renderer is null || !renderer.IsValid() ) return;\n\t\tif ( culled ) CulledRenderers.Add( renderer );\n\t\telse CulledRenderers.Remove( renderer );\n\t}\n\n\tpublic static void Publish( BoBiCoSoftBoneChain chain, SkinnedModelRenderer renderer, List\u003CSoftBoneChainColliderSnapshot\u003E shapes )\n\t{\n\t\tPublishedShapes[chain] = new List\u003CSoftBoneChainColliderSnapshot\u003E( shapes );\n\t}\n\n\tpublic static void BeginStep( Scene scene )\n\t{\n\t\tQueryShapes.Clear();\n\t\tforeach ( var entry in PublishedShapes )\n\t\t\tQueryShapes.Add( entry.Key, new List\u003CSoftBoneChainColliderSnapshot\u003E( entry.Value ) );\n\n\t\tQueryBodyShapes.Clear();\n\t\tif ( scene is null )\n\t\t\treturn;\n\n\t\tforeach ( var collider in scene.GetAllComponents\u003CBoBiCoSoftBoneCollider\u003E() )\n\t\t{\n\t\t\tif ( collider is null || !collider.Enabled )\n\t\t\t\tcontinue;\n\n\t\t\tvar renderer = collider.GetOwningRenderer();\n\t\t\tif ( renderer is not null \u0026\u0026 CulledRenderers.Contains( renderer ) )\n\t\t\t\tcontinue;\n\t\t\tif ( collider.TryGetWorldShape( renderer, out var shape ) )\n\t\t\t\tQueryBodyShapes.Add( new SoftBoneBodyColliderSnapshot( renderer, shape ) );\n\t\t}\n\t}\n\n\tpublic static void Remove( BoBiCoSoftBoneChain chain )\n\t{\n\t\tPublishedShapes.Remove( chain );\n\t\tQueryShapes.Remove( chain );\n\t}\n\n\tpublic static void CollectChainShapes( BoBiCoSoftBoneChain owner, SkinnedModelRenderer renderer, SoftBoneCollisionMode mode, List\u003CSoftBoneColliderShape\u003E output )\n\t{\n\t\tif ( mode == SoftBoneCollisionMode.BaseCollision )\n\t\t\treturn;\n\n\t\tforeach ( var entry in QueryShapes )\n\t\t{\n\t\t\tif ( entry.Key == owner || !entry.Key.Enabled )\n\t\t\t\tcontinue;\n\n\t\t\tvar sameRenderer = entry.Value.Count \u003E 0 \u0026\u0026 entry.Value[0].Renderer == renderer;\n\t\t\tif ( !sameRenderer \u0026\u0026 mode != SoftBoneCollisionMode.FullCollision )\n\t\t\t\tcontinue;\n\n\t\t\tforeach ( var snapshot in entry.Value )\n\t\t\t\toutput.Add( snapshot.Shape );\n\t\t}\n\t}\n\n\tpublic static void CollectExternalBodyShapes( SkinnedModelRenderer ownerRenderer, List\u003CSoftBoneColliderShape\u003E output )\n\t{\n\t\tforeach ( var snapshot in QueryBodyShapes )\n\t\t\tif ( snapshot.Renderer != ownerRenderer )\n\t\t\t\toutput.Add( snapshot.Shape );\n\t}\n}\n"},{"Ident":"bobicolab.bobicosoftbones","Path":"Code/BoBiCo Soft-Physics/SoftBoneCollisionRegistry.cs","FileName":"SoftBoneCollisionRegistry.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"using System.Collections.Generic;\nusing Sandbox;\nusing System.Linq;\n\n\npublic readonly struct SoftBoneChainColliderSnapshot\n{\n\tpublic readonly SkinnedModelRenderer Renderer;\n\tpublic readonly SoftBoneColliderShape Shape;\n\n\tpublic SoftBoneChainColliderSnapshot( SkinnedModelRenderer renderer, SoftBoneColliderShape shape )\n\t{\n\t\tRenderer = renderer;\n\t\tShape = shape;\n\t}\n}\n\npublic readonly struct SoftBoneBodyColliderSnapshot\n{\n\tpublic readonly SkinnedModelRenderer Renderer;\n\tpublic readonly SoftBoneColliderShape Shape;\n\n\tpublic SoftBoneBodyColliderSnapshot( SkinnedModelRenderer renderer, SoftBoneColliderShape shape )\n\t{\n\t\tRenderer = renderer;\n\t\tShape = shape;\n\t}\n}\n\n/// \u003Csummary\u003EPublishes the previous solved link shapes used by cross-chain contacts.\u003C/summary\u003E\npublic static class SoftBoneCollisionRegistry\n{\n\tprivate static readonly Dictionary\u003CBoBiCoSoftBoneChain, List\u003CSoftBoneChainColliderSnapshot\u003E\u003E PublishedShapes = new();\n\tprivate static readonly Dictionary\u003CBoBiCoSoftBoneChain, List\u003CSoftBoneChainColliderSnapshot\u003E\u003E QueryShapes = new();\n\tprivate static readonly List\u003CSoftBoneBodyColliderSnapshot\u003E QueryBodyShapes = new();\n\tprivate static readonly HashSet\u003CSkinnedModelRenderer\u003E CulledRenderers = new();\n\n\tpublic static void SetRendererCulled( SkinnedModelRenderer renderer, bool culled )\n\t{\n\t\tif ( renderer is null || !renderer.IsValid() ) return;\n\t\tif ( culled ) CulledRenderers.Add( renderer );\n\t\telse CulledRenderers.Remove( renderer );\n\t}\n\n\tpublic static void Publish( BoBiCoSoftBoneChain chain, SkinnedModelRenderer renderer, List\u003CSoftBoneChainColliderSnapshot\u003E shapes )\n\t{\n\t\tPublishedShapes[chain] = new List\u003CSoftBoneChainColliderSnapshot\u003E( shapes );\n\t}\n\n\tpublic static void BeginStep( Scene scene )\n\t{\n\t\tQueryShapes.Clear();\n\t\tforeach ( var entry in PublishedShapes )\n\t\t\tQueryShapes.Add( entry.Key, new List\u003CSoftBoneChainColliderSnapshot\u003E( entry.Value ) );\n\n\t\tQueryBodyShapes.Clear();\n\t\tif ( scene is null )\n\t\t\treturn;\n\n\t\tforeach ( var collider in scene.GetAllComponents\u003CBoBiCoSoftBoneCollider\u003E() )\n\t\t{\n\t\t\tif ( collider is null || !collider.Enabled )\n\t\t\t\tcontinue;\n\n\t\t\tvar renderer = collider.GetOwningRenderer();\n\t\t\tif ( renderer is not null \u0026\u0026 CulledRenderers.Contains( renderer ) )\n\t\t\t\tcontinue;\n\t\t\tif ( collider.TryGetWorldShape( renderer, out var shape ) )\n\t\t\t\tQueryBodyShapes.Add( new SoftBoneBodyColliderSnapshot( renderer, shape ) );\n\t\t}\n\t}\n\n\tpublic static void Remove( BoBiCoSoftBoneChain chain )\n\t{\n\t\tPublishedShapes.Remove( chain );\n\t\tQueryShapes.Remove( chain );\n\t}\n\n\tpublic static void CollectChainShapes( BoBiCoSoftBoneChain owner, SkinnedModelRenderer renderer, SoftBoneCollisionMode mode, List\u003CSoftBoneColliderShape\u003E output )\n\t{\n\t\tif ( mode == SoftBoneCollisionMode.BaseCollision )\n\t\t\treturn;\n\n\t\tforeach ( var entry in QueryShapes )\n\t\t{\n\t\t\tif ( entry.Key == owner || !entry.Key.Enabled )\n\t\t\t\tcontinue;\n\n\t\t\tvar sameRenderer = entry.Value.Count \u003E 0 \u0026\u0026 entry.Value[0].Renderer == renderer;\n\t\t\tif ( !sameRenderer \u0026\u0026 mode != SoftBoneCollisionMode.FullCollision )\n\t\t\t\tcontinue;\n\n\t\t\tforeach ( var snapshot in entry.Value )\n\t\t\t\toutput.Add( snapshot.Shape );\n\t\t}\n\t}\n\n\tpublic static void CollectExternalBodyShapes( SkinnedModelRenderer ownerRenderer, List\u003CSoftBoneColliderShape\u003E output )\n\t{\n\t\tforeach ( var snapshot in QueryBodyShapes )\n\t\t\tif ( snapshot.Renderer != ownerRenderer )\n\t\t\t\toutput.Add( snapshot.Shape );\n\t}\n}\n"},{"Ident":"bobicolab.bobicosoftbones","Path":"BoBiCo Soft-Physics/Components/BoBiCoSoftBoneCollider.cs","FileName":"BoBiCoSoftBoneCollider.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"//=========================================================================================================================\n// BoBiCo Soft-Bone Collider Component\n//=========================================================================================================================\n\nusing System;\n\n[Title( \u0022SoftBone Collider\u0022 )]\n[Category( \u0022BoBiCo\u0022 )]\n[Icon( \u0022radio_button_unchecked\u0022 )]\n[HelpUrl( \u0022https://bobico-lab.vercel.app/softbone-docs/bobico-softbones/core-components/softbone-collider\u0022 )]\npublic sealed class BoBiCoSoftBoneCollider : Component, Component.ExecuteInEditor\n{\npublic enum ColliderShape\n{\n\t/// \u003Csummary\u003E\n\t/// A simple spherical collider.\n\t/// Best for rounded body parts and lightweight collision detection.\n\t/// \u003C/summary\u003E\n\tSphere,\n\n\t/// \u003Csummary\u003E\n\t/// A capsule-shaped collider.\n\t/// Recommended for most bones and elongated body parts.\n\t/// \u003C/summary\u003E\n\tCapsule,\n\n\t/// \u003Csummary\u003E\n\t/// A box-shaped collider.\n\t/// Useful for broad collision areas and block-like shapes.\n\t/// \u003C/summary\u003E\n\tBox,\n\n\t/// \u003Csummary\u003E\n\t/// A one-sided flat collider.\n\t/// Useful for flat surface areas.\n\t/// \u003C/summary\u003E\n\tFlat,\n}\n\n//=========================================================================================================================\n// Collider Properties\n//=========================================================================================================================\n\n\t/// \u003Csummary\u003E\n\t/// Offsets the position of the collider.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 1 ), Title( \u0022Position Offset\u0022 ), Group( \u0022Collider Properties\u0022 )]\n\tpublic Vector3 PositionOffset { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Offsets the rotation of the collider.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 2 ), Title( \u0022Rotation Offset\u0022 ), Group( \u0022Collider Properties\u0022 )]\n\tpublic Angles RotationOffset { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Target bone to which the collider is attached. \n\t/// \u003C/summary\u003E\n\t[Property, Order( 3 ), Title( \u0022Target Bone\u0022 ), Group( \u0022Collider Properties\u0022 )]\n\tpublic GameObject TargetBone { get; set; }\n\n//=========================================================================================================================\n// Shape Settings\n//=========================================================================================================================\n\n    /// \u003Csummary\u003E\n    /// Shape used for this chain\u0027s collision volume.\n    /// Different shapes provide different collision behavior and coverage.\n    /// \u003C/summary\u003E\n\t[Property, Order( 4 ), Title( \u0022Collider Shape\u0022 ), Group( \u0022Shape Settings\u0022 )]\n\tpublic ColliderShape Shape { get; set; } = ColliderShape.Sphere;\n\n    /// \u003Csummary\u003E\n    /// Radius of the chain\u0027s collision volume.\n    /// Larger values create thicker collision boundaries.\n    /// \u003C/summary\u003E\n\t[Property, Order( 5 ), Title( \u0022Collider Radius\u0022 ), Group( \u0022Shape Settings\u0022 ), HideIf( nameof( Shape ), ColliderShape.Box )]\n\tpublic float ColliderRadius { get; set; } = 2f;\n\n    /// \u003Csummary\u003E\n    /// Height of Capsule collider.\n    /// \u003C/summary\u003E\n\t[Property, Order( 6 ), Title( \u0022Collider Height\u0022 ), Group( \u0022Shape Settings\u0022 ), ShowIf( nameof( Shape ), ColliderShape.Capsule )]\n\tpublic float ColliderHeight { get; set; } = 10f;\n\n    /// \u003Csummary\u003E\n    /// Size of Box colliders.\n    /// Only applies when the collider shape is set to Box.\n    /// \u003C/summary\u003E\n\t[Property, Order( 7 ), Title( \u0022Box Dimension\u0022 ), Group( \u0022Shape Settings\u0022 ), ShowIf( nameof( Shape ), ColliderShape.Box )]\n\tpublic Vector3 BoxSize { get; set; } = new Vector3( 1.25f, 1.25f, 1.25f );\n\n//=========================================================================================================================\n// Collider Gizmo Settings\n//=========================================================================================================================\n\n    /// \u003Csummary\u003E\n    /// Displays the chain\u0027s collider in the Scene view.\n    /// Useful for visualizing collision shapes and sizes.\n    /// \u003C/summary\u003E\n\t[Property, Order( 8 ), Title( \u0022Collider Gizmo\u0022 ), Group( \u0022Gizmo Settings\u0022 )]\n\tpublic bool ShowColliderGizmo { get; set; } = true;\n\n    /// \u003Csummary\u003E\n    /// Keeps the collider gizmo visible even when the chain is not selected.\n    /// Useful for debugging collision setups.\n    /// \u003C/summary\u003E\n\t[Property, Order( 9 ), Title( \u0022Always Show Gizmo\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowColliderGizmo ), true )]\n\tpublic bool AlwaysShowGizmo { get; set; }\n\n    /// \u003Csummary\u003E\n    /// Color used for rendering the collider gizmo.\n    /// \u003C/summary\u003E\n\t[Property, Order( 10 ), Title( \u0022Collider Gizmo Color\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowColliderGizmo ), true )]\n\tpublic Color ColliderGizmoColor { get; set; } = Gizmo.Colors.Green;\n\n    /// \u003Csummary\u003E\n    /// Controls the transparency of the collider gizmo.\n    /// Lower values make the visualization less intrusive.\n    /// \u003C/summary\u003E\n\t[Property, Order( 11 ), Range( 0, 1 ), Title( \u0022Gizmo Opacity\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowColliderGizmo ), true )]\n\tpublic float ColliderGizmoOpacity { get; set; } = 1f;\t\n\n//=========================================================================================================================\n// Collider Shape Calculation\n//=========================================================================================================================\n\tpublic bool TryGetWorldShape( SkinnedModelRenderer renderer, out SoftBoneColliderShape shape )\n\t{\n\t\tshape = default;\n\t\tvar boneName = TargetBone is not null \u0026\u0026 TargetBone.IsValid() ? TargetBone.Name?.Trim() : \u0022\u0022;\n\t\tif ( string.IsNullOrWhiteSpace( boneName ) )\n\t\t\treturn false;\n\n\t\tvar hasBoneTransform = renderer is not null \u0026\u0026 renderer.IsValid() \u0026\u0026 renderer.Model?.Bones is not null \u0026\u0026 renderer.Model.Bones.HasBone( boneName );\n\t\tif ( hasBoneTransform )\n\t\t{\n\t\t\tvar bone = renderer.Model.Bones.GetBone( boneName );\n\t\t\tif ( renderer.TryGetBoneTransformAnimation( bone, out var animatedTransform ) || renderer.TryGetBoneTransform( bone, out animatedTransform ) )\n\t\t\t\treturn BuildWorldShape( animatedTransform, out shape );\n\t\t}\n\n\t\treturn BuildWorldShape( TargetBone.WorldTransform, out shape );\n\t}\n\n\tprivate bool BuildWorldShape( Transform boneTransform, out SoftBoneColliderShape shape )\n\t{\n\t\tshape = default;\n\n\t\t// Swap pitch/yaw to match S\u0026box bone forward = X axis\n\t\tvar fixedRotOffset = new Angles( RotationOffset.roll, RotationOffset.pitch, RotationOffset.yaw );\n\t\tvar rotation = boneTransform.Rotation * fixedRotOffset.ToRotation();\n\t\tvar radius = Math.Max( ColliderRadius, 0.001f );\n\t\tvar height = Math.Max( ColliderHeight, radius * 2f );\n\t\t// Collider offsets use the existing VRC-style axis order: X=up, Y=forward, Z=right.\n\t\tvar remappedOffset = new Vector3( PositionOffset.y, PositionOffset.z, PositionOffset.x );\n\t\tvar center = boneTransform.PointToWorld( remappedOffset );\n\n\t\tif ( Shape == ColliderShape.Sphere || (Shape == ColliderShape.Capsule \u0026\u0026 height \u003C= radius * 2f) )\n\t\t{\n\t\t\tshape = SoftBoneColliderShape.Sphere( center, radius );\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( Shape == ColliderShape.Capsule )\n\t\t{\n\t\t\tvar halfLine = Math.Max( 0f, height * 0.5f - radius );\n\t\t\t// VRC capsules are authored along their local Up axis. Using Forward here\n\t\t\t// made S\u0026box Roll rotate around the capsule\u0027s own length, which is invisible.\n\t\t\tvar axis = rotation * Vector3.Forward;\n\t\t\tshape = SoftBoneColliderShape.Capsule( center - axis * halfLine, center \u002B axis * halfLine, radius );\n\t\t}\n\t\telse if ( Shape == ColliderShape.Box )\n\t\t\tshape = SoftBoneColliderShape.Box( center, rotation, new Vector3( Math.Max( BoxSize.z, 0.001f ), Math.Max( BoxSize.x, 0.001f ), Math.Max( BoxSize.y, 0.001f ) ) );\n\t\telse\n\t\t\tshape = SoftBoneColliderShape.Flat( center, rotation * Vector3.Up );\n\t\treturn true;\n\t}\n\n//=========================================================================================================================\n// Collider Gizmo Drawing\n//=========================================================================================================================\n\n\tprotected override void DrawGizmos()\n\t{\n\t\tbase.DrawGizmos();\n\t\tDrawColliderGizmo( AlwaysShowGizmo || Gizmo.IsSelected );\n\t}\n\n\tinternal void DrawColliderGizmo( bool visible, bool force = false )\n\t{\n\t\tif ( !visible || ( !force \u0026\u0026 !ShowColliderGizmo ) || !TryGetWorldShape( FindRenderer(), out var shape ) )\n\t\t\treturn;\n\n\t\t// Draw directly in world space so the sphere radius is never distorted by\n\t\t// the component\u0027s scale (which caused the squished ellipsoid).\n\t\tusing ( Gizmo.Scope( \u0022SoftBoneCollider\u0022 ) )\n\t\t{\n\t\t\tGizmo.Transform = new Transform( Vector3.Zero, Rotation.Identity, 1f );\n\t\t\tGizmo.Draw.IgnoreDepth = true;\n\t\t\tGizmo.Draw.LineThickness = 1;\n\t\t\tGizmo.Draw.Color = ColliderGizmoColor.WithAlpha( ColliderGizmoOpacity );\n\n\t\t\tif ( shape.IsSphere )\n\t\t\t\tGizmo.Draw.LineSphere( new Sphere( shape.Start, shape.Radius ) );\n\t\t\telse if ( shape.Type == SoftBoneColliderShapeType.Capsule )\n\t\t\t\tGizmo.Draw.LineCapsule( new Capsule( shape.Start, shape.End, shape.Radius ) );\n\t\t\telse if ( shape.Type == SoftBoneColliderShapeType.Box )\n\t\t\t{\n\t\t\t\tGizmo.Transform = new Transform( shape.Center, shape.Rotation, 1f );\n\t\t\t\tGizmo.Draw.LineBBox( BBox.FromPositionAndSize( Vector3.Zero, shape.Size ) );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tvar tangent = Vector3.Cross( shape.Normal, Math.Abs( Vector3.Dot( shape.Normal, Vector3.Up ) ) \u003C 0.99f ? Vector3.Up : Vector3.Right ).Normal;\n\t\t\t\tvar bitangent = Vector3.Cross( shape.Normal, tangent ).Normal;\n\t\t\t\tvar size = ColliderRadius;\n\t\t\t\tvar a = shape.Center \u002B (tangent \u002B bitangent) * size;\n\t\t\t\tvar b = shape.Center \u002B (tangent - bitangent) * size;\n\t\t\t\tvar c = shape.Center \u002B (-tangent - bitangent) * size;\n\t\t\t\tvar d = shape.Center \u002B (-tangent \u002B bitangent) * size;\n\t\t\t\tGizmo.Draw.Line( a, b ); Gizmo.Draw.Line( b, c ); Gizmo.Draw.Line( c, d ); Gizmo.Draw.Line( d, a );\n\t\t\t\tGizmo.Draw.Line( shape.Center, shape.Center \u002B shape.Normal * 8f );\n\t\t\t}\n\t\t}\n\t}\n\n//=========================================================================================================================\n// Collider Bone Lookup\n//=========================================================================================================================\n\n\tprivate SkinnedModelRenderer FindRenderer()\n\t{\n\t\tvar targetRenderer = TargetBone?.GetComponentInParent\u003CSkinnedModelRenderer\u003E( true, true );\n\t\tif ( targetRenderer is not null \u0026\u0026 targetRenderer.IsValid() )\n\t\t\treturn targetRenderer;\n\t\treturn null;\n\t}\n\n\tinternal SkinnedModelRenderer GetOwningRenderer() =\u003E FindRenderer();\n\n\tprotected override void OnValidate()\n\t{\n\t\tColliderRadius = Math.Max( ColliderRadius, 0.001f );\n\t\tColliderHeight = Math.Max( ColliderHeight, ColliderRadius * 2f );\n\t\tBoxSize = new Vector3( Math.Max( BoxSize.x, 0.001f ), Math.Max( BoxSize.y, 0.001f ), Math.Max( BoxSize.z, 0.001f ) );\n\t\tColliderGizmoOpacity = Math.Clamp( ColliderGizmoOpacity, 0f, 1f );\n\t}\n}"},{"Ident":"bobicolab.bobicosoftbones","Path":"BoBiCo Soft-Physics/Components/BoBiCoSoftBoneColliderGroup.cs","FileName":"BoBiCoSoftBoneColliderGroup.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"//=========================================================================================================================\n// BoBiCo Soft-Bone Collider Group Component\n//=========================================================================================================================\n\nusing System.Collections.Generic;\n\n[Title( \u0022SoftBone Collider Group\u0022 )]\n[Category( \u0022BoBiCo\u0022 )]\n[Icon( \u0022category\u0022 )]\n[HelpUrl( \u0022https://bobico-lab.vercel.app/softbone-docs/bobico-softbones/core-components/softbone-collider-group\u0022 )]\npublic sealed class BoBiCoSoftBoneColliderGroup : Component, Component.ExecuteInEditor\n{\n\n//=========================================================================================================================\n// Group Setup\n//=========================================================================================================================\n\n\t/// \u003Csummary\u003E\n\t/// List of colliders that are part of this group. These colliders will be used for collision detection with soft-bone chains.\n\t/// \u003C/summary\u003E\n\t[Property, Title( \u0022Colliders\u0022 ), Group( \u0022Colliders\u0022 )]\n\tpublic List\u003CGameObject\u003E Colliders { get; set; } = new();\n\n    // Collect all valid colliders from the group and add them to the output list, avoiding duplicates using the seen set.\n\tpublic void CollectColliders( List\u003CBoBiCoSoftBoneCollider\u003E output, HashSet\u003CBoBiCoSoftBoneCollider\u003E seen )\n\t{\n\t\tif ( !Enabled || Colliders is null )\n\t\t\treturn;\n\n\t\tforeach ( var colliderObject in Colliders )\n\t\t{\n\t\t\tif ( colliderObject is null || !colliderObject.IsValid() )\n\t\t\t\tcontinue;\n\n\t\t\tvar collider = colliderObject.GetComponent\u003CBoBiCoSoftBoneCollider\u003E();\n\t\t\tif ( collider is not null \u0026\u0026 collider.Enabled \u0026\u0026 seen.Add( collider ) )\n\t\t\t\toutput.Add( collider );\n\t\t}\n\t}\n}\n"},{"Ident":"bobicolab.bobicosoftbones","Path":"BoBiCo Soft-Physics/SoftBoneJointState.cs","FileName":"SoftBoneJointState.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"using System;\n\n/// \u003Csummary\u003ERuntime state for one simulated SoftBone joint.\u003C/summary\u003E\npublic sealed class SoftBoneJointState\n{\n\tpublic BoneCollection.Bone Bone;\n\n\t// Rest-pose data\n\tpublic float Length;\n\tpublic Vector3 LocalPoseVector;\n\tpublic Transform RestTransformLocal;\n\tpublic Vector3 OriginalLocalGravityNormal;\n\n\t// Per-frame state. Positions are renderer-local unless suffixed with World.\n\tpublic Vector3 PreviousBegin;\n\tpublic Vector3 PreviousEnd;\n\tpublic Vector3 PreviousAnimatedBegin;\n\tpublic Rotation PreviousAnimatedRotation;\n\tpublic Rotation PreviousSimulationRotation;\n\tpublic Vector3 ImmobileEnd;\n\tpublic Vector3 PreviousEndWorld;\n\tpublic Vector3 PreviousVector;\n\tpublic Vector3 PreviousVelocity;\n\tpublic bool HasHistory;\n}\n"},{"Ident":"bobicolab.bobicosoftbones","Path":"Code/BoBiCo Soft-Physics/SoftBoneAssembly.cs","FileName":"SoftBoneAssembly.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"// I don\u0027t know, I kept these here to avoid random errors.\r\n\r\nglobal using Sandbox;\r\nglobal using System.Collections.Generic;\r\nglobal using System.Linq;"},{"Ident":"bobicolab.bobicosoftbones","Path":"BoBiCo Soft-Physics/SoftBoneSkinnedModelUtility.cs","FileName":"SoftBoneSkinnedModelUtility.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"using System;\n\npublic static class SoftBoneSkinnedModelUtility\n{\n\tprivate const float Epsilon = 0.000008f;\n\n\tpublic static bool TryGetBoneAnimationLocal( SkinnedModelRenderer renderer, BoneCollection.Bone bone, out Transform transform )\n\t{\n\t\ttransform = default;\n\n\t\tif ( renderer is null || !renderer.IsValid() || bone is null )\n\t\t\treturn false;\n\n\t\tif ( renderer.TryGetBoneTransformAnimation( bone, out var worldTransform ) )\n\t\t{\n\t\t\ttransform = renderer.WorldTransform.ToLocal( worldTransform );\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( renderer.TryGetBoneTransform( bone, out worldTransform ) )\n\t\t{\n\t\t\ttransform = renderer.WorldTransform.ToLocal( worldTransform );\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic static Vector3 ToWorld( SkinnedModelRenderer renderer, Vector3 localPosition )\n\t{\n\t\tif ( renderer is null || !renderer.IsValid() )\n\t\t\treturn localPosition;\n\n\t\treturn renderer.WorldTransform.PointToWorld( localPosition );\n\t}\n\n\tpublic static Vector3 ToLocal( SkinnedModelRenderer renderer, Vector3 worldPosition )\n\t{\n\t\tif ( renderer is null || !renderer.IsValid() )\n\t\t\treturn worldPosition;\n\n\t\treturn renderer.WorldTransform.PointToLocal( worldPosition );\n\t}\n\n\tpublic static Vector3 SafeNormal( Vector3 value, Vector3 fallback )\n\t{\n\t\tif ( value.Length \u003C= Epsilon )\n\t\t\treturn fallback.Length \u003E Epsilon ? fallback.Normal : Vector3.Forward;\n\n\t\treturn value.Normal;\n\t}\n}\n"},{"Ident":"bobicolab.bobicosoftbones","Path":"Code/BoBiCo Soft-Physics/Components/BoBiCoSoftBoneChain.cs","FileName":"BoBiCoSoftBoneChain.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"//=========================================================================================================================\n// BoBiCo Soft-bone Chain Component\n//=========================================================================================================================\n\nusing System;\nusing System.Collections.Generic;\n\npublic enum SoftBoneLinkColliderShape\n{\n\t/// \u003Csummary\u003E\n\t/// A simple spherical collider.\n\t/// Best for rounded body parts and lightweight collision detection.\n\t/// \u003C/summary\u003E\n\tSphere,\n\n\t/// \u003Csummary\u003E\n\t/// A capsule-shaped collider.\n\t/// Recommended for most bones and elongated body parts.\n\t/// \u003C/summary\u003E\n\tCapsule,\n\n\t/// \u003Csummary\u003E\n\t/// A box-shaped collider.\n\t/// Useful for broad collision areas and block-like shapes.\n\t/// \u003C/summary\u003E\n\tBox,\n\n\t/// \u003Csummary\u003E\n\t/// A one-sided flat collider.\n\t/// Useful for flat surface areas.\n\t/// \u003C/summary\u003E\n\tFlat,\n}\n\npublic enum SoftBoneCollisionMode\n{\n\t/// \u003Csummary\u003E\n\t/// Disables all collision detection for this chain.\n\t/// \u003C/summary\u003E\n\tNone,\n\n\t/// \u003Csummary\u003E\n\t/// Collides only with body colliders.\n\t/// This is the recommended mode for most setups.\n\t/// \u003C/summary\u003E\n\tBaseCollision,\n\n\t/// \u003Csummary\u003E\n\t/// Collides with body colliders and collider chains from the same model.\n\t/// \u003C/summary\u003E\n\tSelfCollision,\n\n\t/// \u003Csummary\u003E\n\t/// Collides with body colliders and collider chains from all SoftBone-enabled models.\n\t/// \u003C/summary\u003E\n\tFullCollision\n}\n\n[Title( \u0022SoftBone Chain\u0022 )]\n[Category( \u0022BoBiCo\u0022 )]\n[Icon( \u0022linear_scale\u0022 )]\n[HelpUrl( \u0022https://bobico-lab.vercel.app/softbone-docs/bobico-softbones/core-components/softbone-chain\u0022 )]\npublic sealed class BoBiCoSoftBoneChain : Component, Component.ExecuteInEditor\n{\n\t// prevents unstable math when values are extremely close to zero.\n\tprivate const float Epsilon = 0.000008f;\n\n\tprivate GameObject _targetBone;\n\tprivate SoftBoneControlMode _controlMode;\n\tprivate bool _curveDefaultsInitialized;\n\n//=========================================================================================================================\n// Chain Properties Section\n//=========================================================================================================================\n\n\t/// \u003Csummary\u003E\n\t/// Defines the root bone of the chain that\u0027ll be simulated.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 1 ), Title( \u0022Target Bone\u0022 ), Group( \u0022Chain Properties\u0022 )]\n\tpublic GameObject TargetBone\n\t{\n\t\tget =\u003E _targetBone;\n\t\tset\n\t\t{\n\t\t\tif ( _targetBone == value )\n\t\t\t\treturn;\n\n\t\t\t_targetBone = value;\n\t\t\tUpdateOwnerName();\n\t\t\tMarkDirty();\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Rotates the chain in local space.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 2 ), Title( \u0022Rotation Offset\u0022 ), Group( \u0022Chain Properties\u0022 )]\n\tpublic Angles RotationOffset { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Control mode for the SoftBone physics parameters. \n\t/// Slider mode uses a single value for each parameter, \n\t/// Range mode uses a min/max range for each parameter, \n\t/// and Curve mode uses a curve to define the parameter values along the chain.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 3 ), Title( \u0022Control Mode\u0022 ), Group( \u0022Chain Properties\u0022 )]\n\tpublic SoftBoneControlMode ControlMode\n\t{\n\t\tget =\u003E _controlMode;\n\t\tset\n\t\t{\n\t\t\tif ( _controlMode == value ) return;\n\t\t\t_controlMode = value;\n\t\t\tif ( value == SoftBoneControlMode.Curve \u0026\u0026 !_curveDefaultsInitialized ) InitializeCurveDefaults();\n\t\t}\n\t}\n\n\tpublic enum SoftBoneControlMode\n    {\n        Slider,\n        Range,\n        Curve\n    }\n\n\t/// \u003Csummary\u003E\n\t/// When enabled, the root bone itself is also physically simulated\n\t/// (wobbles independently). When disabled, only child bones simulate\n\t/// and the root follows animation exactly.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 4 ), Title( \u0022Simulate Root\u0022 ), Group( \u0022Chain Properties\u0022 )]\n\tpublic bool SimulateRoot { get; set; } = false;\n\n\t/// \u003Csummary\u003E\n\t/// When enabled, only the first X chain bones are simulated.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 5 ), Title( \u0022Limit Chain Length\u0022 ), Group( \u0022Chain Properties\u0022 )]\n\tpublic bool LimitChainLength { get; set; } = false;\n\n\t/// \u003Csummary\u003E\n\t/// Limit the amount of chain bones from tip to root that are simulated when Limit Children is enabled.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 6 ), Title( \u0022Chain Length Limit\u0022 ), Group( \u0022Chain Properties\u0022 ), ShowIf( nameof( LimitChainLength ), true )]\n\tpublic int ChainLimit { get; set; } = 0;\n\n//=========================================================================================================================\n// Physics Settings\n//=========================================================================================================================\n\n\t/// \u003Csummary\u003E\t\n\t/// Controls how strongly the chain returns toward its animated rest pose.\n\t/// Higher values reduce swaying and make the chain settle more quickly.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 8 ), Range( 0, 1 ), Title( \u0022Drag\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Pull { get; set; } = 0.20f;\n\n\t/// \u003Csummary\u003E\t\n\t/// Controls how strongly the chain returns toward its animated rest pose.\n\t/// Higher values reduce swaying and make the chain settle more quickly.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 8 ), Range( 0, 1 ), Title( \u0022Drag\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 PullRange { get; set; } = new( 0.20f, 0.20f );\n\n\t/// \u003Csummary\u003E\t\n\t/// Controls how strongly the chain returns toward its animated rest pose.\n\t/// Higher values reduce swaying and make the chain settle more quickly.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 8 ), Title( \u0022Drag\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve PullCurve { get; set; } = ConstantCurve( 0.20f );\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much spring force is applied to the chain.\n\t/// Higher values create softer, bouncier motion, while lower values produce more stable movement.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 9 ), Range( 0, 1 ), Title( \u0022Spring\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Spring { get; set; } = 0.50f;\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much spring force is applied to the chain.\n\t/// Higher values create softer, bouncier motion, while lower values produce more stable movement.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 9 ), Range( 0, 1 ), Title( \u0022Spring\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 SpringRange { get; set; } = new( 0.50f, 0.50f );\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much spring force is applied to the chain.\n\t/// Higher values create softer, bouncier motion, while lower values produce more stable movement.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 9 ), Title( \u0022Spring\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve SpringCurve { get; set; } = ConstantCurve( 0.50f );\n\n\n\t/// \u003Csummary\u003E\n\t/// Controls how resistant the chain is to bending.\n\t/// Higher values make the chain feel firmer, while lower values allow more flexibility.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 10 ), Range( 0, 1 ), Title( \u0022Stiffness\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Stiffness { get; set; } = 0.30f;\n\n\t/// \u003Csummary\u003E\n\t/// Controls how resistant the chain is to bending.\n\t/// Higher values make the chain feel firmer, while lower values allow more flexibility.\n\t/// \u003C/summary\u003E\t\n\t[Property, Order( 10 ), Range( 0, 1 ), Title( \u0022Stiffness\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 StiffnessRange { get; set; } = new( 0.30f, 0.30f );\n\n\t/// \u003Csummary\u003E\n\t/// Controls how resistant the chain is to bending.\n\t/// Higher values make the chain feel firmer, while lower values allow more flexibility.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 10 ), Title( \u0022Stiffness\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve StiffnessCurve { get; set; } = ConstantCurve( 0.30f );\n\n\t/// \u003Csummary\u003E\n\t/// Applies a constant directional force to the chain.\n\t/// Positive values pull downward, while negative values pull upward.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 11 ), Range( -1, 1 ), Title( \u0022Gravity\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Gravity { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Applies a constant directional force to the chain.\n\t/// Positive values pull downward, while negative values pull upward.Positive values pull the chain down, negative values pull the chain up.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 11 ), Range( 0, 1 ), Title( \u0022Gravity\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 GravityRange { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Applies a constant directional force to the chain.\n\t/// Positive values pull downward, while negative values pull upward.Positive values pull the chain down, negative values pull the chain up.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 11 ), Title( \u0022Gravity\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve GravityCurve { get; set; } = ConstantCurve( 0f );\n\n\t/// \u003Csummary\u003E\n\t/// Reduces gravity while the chain is near its animated rest pose.\n\t/// Higher values keep idle chains more stable while preserving gravity during motion.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 12 ), Range( 0, 1 ), Title( \u0022Gravity Falloff\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float GravityFalloff { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Reduces gravity while the chain is near its animated rest pose.\n\t/// Higher values keep idle chains more stable while preserving gravity during motion.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 12 ), Range( 0, 1 ), Title( \u0022Gravity Falloff\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 GravityFalloffRange { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Reduces gravity while the chain is near its animated rest pose.\n\t/// Higher values keep idle chains more stable while preserving gravity during motion.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 12 ), Title( \u0022Gravity Falloff\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve GravityFalloffCurve { get; set; } = ConstantCurve( 0f );\n\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much the chain can extend beyond its rest length.\n\t/// Higher values allow longer, softer stretching under force.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 13 ), Range( 0, 2 ), Title( \u0022Stretch\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Stretch { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much the chain can extend beyond its rest length.\n\t/// Higher values allow longer, softer stretching under force.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 13 ), Range( 0, 2 ), Title( \u0022Stretch\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 StretchRange { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much the chain can extend beyond its rest length.\n\t/// Higher values allow longer, softer stretching under force.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 13 ), Title( \u0022Stretch\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve StretchCurve { get; set; } = ConstantCurve( 0f );\n\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much the chain can compress below its rest length.\n\t/// Higher values allow greater compression.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 14 ), Range( 0, 1 ), Title( \u0022Squish\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Squish { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much the chain can compress below its rest length.\n\t/// Higher values allow greater compression.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 14 ), Range( 0, 1 ), Title( \u0022Squish\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 SquishRange { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Controls how much the chain can compress below its rest length.\n\t/// Higher values allow greater compression.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 14 ), Title( \u0022Squish\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve SquishCurve { get; set; } = ConstantCurve( 0f );\n\n\t/// \u003Csummary\u003E\n\t/// Reduces movement caused by the character\u0027s own motion.\n\t/// Higher values make the chain feel heavier and less reactive while the character moves.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 15 ), Range( 0, 1 ), Title( \u0022Movement Resistance\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Slider )]\n\tpublic float Immobile { get; set; } = 1.00f;\n\n\t/// \u003Csummary\u003E\n\t/// Reduces movement caused by the character\u0027s own motion.\n\t/// Higher values make the chain feel heavier and less reactive while the character moves.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 15 ), Range( 0, 1 ), Title( \u0022Movement Resistance\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Range )]\n\tpublic Vector2 ImmobileRange { get; set; } = new( 1.00f, 1.00f );\n\n\t/// \u003Csummary\u003E\n\t/// Reduces movement caused by the character\u0027s own motion.\n\t/// Higher values make the chain feel heavier and less reactive while the character moves.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 15 ), Title( \u0022Movement Resistance\u0022 ), Group( \u0022Physics Settings\u0022 ), ShowIf( nameof( ControlMode ), SoftBoneControlMode.Curve )]\n\tpublic Curve ImmobileCurve { get; set; } = ConstantCurve( 1.00f );\n\n//=========================================================================================================================\n// Limit Settings Section\n//=========================================================================================================================\n\n    /// \u003Csummary\u003E\n    /// Defines how the chain\u0027s movement is constrained.\n    /// Different limit types provide different ways of restricting bone rotation.\n    /// \u003C/summary\u003E\n\t[Property, Order( 16 ), Title( \u0022Limit Type\u0022 ), Group( \u0022Limit Settings\u0022 )]\n\tpublic SoftBoneLimitType LimitType { get; set; } = SoftBoneLimitType.Cone;\n\n    /// \u003Csummary\u003E\n    /// Maximum angle the chain can rotate away from its rest direction.\n    /// Higher values allow greater freedom of movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 17 ), Range( 0, 180 ), Title( \u0022Max Angle\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxAngleSlider ), true )]\n\tpublic float MaxAngle { get; set; } = 95f;\n\n\t/// \u003Csummary\u003E\n    /// Maximum angle the chain can rotate away from its rest direction.\n    /// Higher values allow greater freedom of movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 17 ), Range( 0, 1 ), Title( \u0022Max Angle\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxAngleRange ), true )]\n\tpublic Vector2 MaxAngleRange { get; set; } = new( 95f, 95f );\n\n\t/// \u003Csummary\u003E\n    /// Maximum angle the chain can rotate away from its rest direction.\n    /// Higher values allow greater freedom of movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 17 ), Title( \u0022Max Angle\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxAngleCurve ), true )]\n\tpublic Curve MaxAngleCurve { get; set; } = ConstantCurve( 95f / 180f );\n\tpublic bool UsesMaxAngle =\u003E LimitType == SoftBoneLimitType.Cone || LimitType == SoftBoneLimitType.Plane;\n\tpublic bool ShowMaxAngleSlider =\u003E UsesMaxAngle \u0026\u0026 ControlMode == SoftBoneControlMode.Slider;\n\tpublic bool ShowMaxAngleRange =\u003E UsesMaxAngle \u0026\u0026 ControlMode == SoftBoneControlMode.Range;\n\tpublic bool ShowMaxAngleCurve =\u003E UsesMaxAngle \u0026\u0026 ControlMode == SoftBoneControlMode.Curve;\n\n    /// \u003Csummary\u003E\n    /// Maximum horizontal rotation allowed for Ellipse limits.\n    /// Increase to allow more side-to-side movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 18 ), Range( 0, 180 ), Title( \u0022Max Yaw\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxYawSlider ), true )]\n\tpublic float MaxYaw { get; set; } = 95f;\n\n\t/// \u003Csummary\u003E\n    /// Maximum horizontal rotation allowed for Ellipse limits.\n    /// Increase to allow more side-to-side movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 18 ), Range( 0, 180 ), Title( \u0022Max Yaw\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxYawRange ), true )]\n\tpublic Vector2 MaxYawRange { get; set; } = new( 95f, 95f );\n\n    /// \u003Csummary\u003E\n    /// Maximum horizontal rotation allowed for Ellipse limits.\n    /// Increase to allow more side-to-side movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 18 ), Title( \u0022Max Yaw\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxYawCurve ), true )]\n\tpublic Curve MaxYawCurve { get; set; } = ConstantCurve( 95f / 180f );\n\tpublic bool ShowMaxYawSlider =\u003E LimitType == SoftBoneLimitType.Ellipse \u0026\u0026 ControlMode == SoftBoneControlMode.Slider;\n\tpublic bool ShowMaxYawRange =\u003E LimitType == SoftBoneLimitType.Ellipse \u0026\u0026 ControlMode == SoftBoneControlMode.Range;\n\tpublic bool ShowMaxYawCurve =\u003E LimitType == SoftBoneLimitType.Ellipse \u0026\u0026 ControlMode == SoftBoneControlMode.Curve;\n\n\n    /// \u003Csummary\u003E\n    /// Maximum vertical rotation allowed for Ellipse limits.\n    /// Increase to allow more up-and-down movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 19 ), Range( 0, 180 ), Title( \u0022Max Pitch\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxPitchSlider ), true )]\n\tpublic float MaxPitch { get; set; } = 95f;\n\n    /// \u003Csummary\u003E\n    /// Maximum vertical rotation allowed for Ellipse limits.\n    /// Increase to allow more up-and-down movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 19 ), Range( 0, 180 ), Title( \u0022Max Pitch\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxPitchRange ), true )]\n\tpublic Vector2 MaxPitchRange { get; set; } = new( 95f, 95f );\n\n    /// \u003Csummary\u003E\n    /// Maximum vertical rotation allowed for Ellipse limits.\n    /// Increase to allow more up-and-down movement.\n    /// \u003C/summary\u003E\n\t[Property, Order( 19 ), Title( \u0022Max Pitch\u0022 ), Group( \u0022Limit Settings\u0022 ), ShowIf( nameof( ShowMaxPitchCurve ), true )]\n\tpublic Curve MaxPitchCurve { get; set; } = ConstantCurve( 95f / 180f );\n\tpublic bool ShowMaxPitchSlider =\u003E LimitType == SoftBoneLimitType.Ellipse \u0026\u0026 ControlMode == SoftBoneControlMode.Slider;\n\tpublic bool ShowMaxPitchRange =\u003E LimitType == SoftBoneLimitType.Ellipse \u0026\u0026 ControlMode == SoftBoneControlMode.Range;\n\tpublic bool ShowMaxPitchCurve =\u003E ControlMode == SoftBoneControlMode.Curve \u0026\u0026 LimitType == SoftBoneLimitType.Ellipse;\n\n\n    /// \u003Csummary\u003E\n    /// Rotates the limit shape relative to the chain.\n    /// \u003C/summary\u003E\n\t[Property, Order( 20 ), Title( \u0022Limit Rotation\u0022 ), Group( \u0022Limit Settings\u0022 ), HideIf( nameof( LimitType ), SoftBoneLimitType.None )]\n\tpublic Angles LimitRotation { get; set; }\n\n    /// \u003Csummary\u003E\n    /// Shape used for this chain\u0027s collision volume.\n    /// Different shapes provide different collision behavior and coverage.\n    /// \u003C/summary\u003E\n\t[Property, Order( 21 ), Title( \u0022Collider Shape\u0022 ), Group( \u0022Collision Settings\u0022 )]\n\tpublic SoftBoneLinkColliderShape ColliderShape { get; set; } = SoftBoneLinkColliderShape.Capsule;\n\n    /// \u003Csummary\u003E\n    /// Controls which colliders this chain can interact with.\n    /// Higher modes enable more realistic but more expensive collision behavior.\n    /// \u003C/summary\u003E\n\t[Property, Order( 22 ), Title( \u0022Collision Mode\u0022 ), Group( \u0022Collision Settings\u0022 )]\n\tpublic SoftBoneCollisionMode CollisionMode { get; set; } = SoftBoneCollisionMode.BaseCollision;\n\n\t/// \u003Csummary\u003E\n\t/// Assign one or more SoftBone Collider Groups for this chain to interact with.\n\t/// Colliders outside of these groups will be ignored.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 23 ), Title( \u0022Collider Groups\u0022 ), Group( \u0022Collision Settings\u0022 )]\n\tpublic List\u003CGameObject\u003E ColliderGroups { get; set; } = new();\n\n    /// \u003Csummary\u003E\n    /// Allows the colliders to collide with the root collider.\n    /// Disable if the root should remain free.\n    /// \u003C/summary\u003E\n\t[Property, Order( 24 ), Title( \u0022Collide With Root\u0022 ), Group( \u0022Collision Settings\u0022 )]\n\tpublic bool CollideWithRoot { get; set; }\n\n\t// Helpers for conditional property visibility\n\tprivate bool ShowRadiusSlider =\u003E ControlMode == SoftBoneControlMode.Slider \u0026\u0026 ColliderShape != SoftBoneLinkColliderShape.Box;\n\tprivate bool ShowRadiusRange  =\u003E ControlMode == SoftBoneControlMode.Range  \u0026\u0026 ColliderShape != SoftBoneLinkColliderShape.Box;\n\tprivate bool ShowRadiusCurve  =\u003E ControlMode == SoftBoneControlMode.Curve  \u0026\u0026 ColliderShape != SoftBoneLinkColliderShape.Box;\n\n    /// \u003Csummary\u003E\n    /// Radius of the chain\u0027s collision volume.\n    /// Larger values create thicker collision boundaries.\n    /// \u003C/summary\u003E\n\t[Property, Order( 25 ), Title( \u0022Collider Radius\u0022 ), Group( \u0022Collision Settings\u0022 ), ShowIf( nameof( ShowRadiusSlider ), true )]\n\tpublic float ColliderRadius { get; set; } = 0.5f;\n\n    /// \u003Csummary\u003E\n    /// Radius of the chain\u0027s collision volume.\n    /// Larger values create thicker collision boundaries.\n    /// \u003C/summary\u003E\n\t[Property, Order( 25 ), Title( \u0022Collider Radius\u0022 ), Group( \u0022Collision Settings\u0022 ), ShowIf( nameof( ShowRadiusRange ), true )]\n\tpublic Vector2 ColliderRadiusRange { get; set; } = new( 0.5f, 0.5f );\n\n    /// \u003Csummary\u003E\n    /// Radius of the chain\u0027s collision volume.\n    /// Larger values create thicker collision boundaries.\n    /// \u003C/summary\u003E\n\t[Property, Order( 25 ), Title( \u0022Collider Radius\u0022 ), Group( \u0022Collision Settings\u0022 ), ShowIf( nameof( ShowRadiusCurve ), true )]\n\tpublic Curve ColliderRadiusCurve { get; set; } = ConstantCurve( 0.5f );\n\n    /// \u003Csummary\u003E\n    /// Height of Capsule collider.\n\t/// Available only when the collider shape is set to Capsule.\n    /// \u003C/summary\u003E\n\t[Property, Order( 26 ), Title( \u0022Collider Height\u0022 ), Group( \u0022Collision Settings\u0022 ), ShowIf( nameof( ColliderShape ), SoftBoneLinkColliderShape.Capsule )]\n\tpublic float ColliderHeight { get; set; } = 1.25f;\n\n    /// \u003Csummary\u003E\n    /// Defines the dimensions of the box collider.\n    /// Available only when the collider shape is set to Box.\n    /// \u003C/summary\u003E\n\t[Property, Order( 27 ), Title( \u0022Box Dimensions\u0022 ), Group( \u0022Collision Settings\u0022 ), ShowIf( nameof( ColliderShape ), SoftBoneLinkColliderShape.Box )]\n\tpublic Vector3 BoxDimension { get; set; } = new Vector3( 1.25f, 1.25f, 1.25f );\n\n//=========================================================================================================================\n// Gizmo Setting Section\n//=========================================================================================================================\n\n    /// \u003Csummary\u003E\n    /// Displays the chain\u0027s angle limit in the Scene view.\n    /// Useful for visualizing and adjusting movement constraints.\n    /// \u003C/summary\u003E\n\t[Property, Order( 28 ), Header( \u0022Limit Gizmo\u0022 ), Title( \u0022Limit Gizmo\u0022 ), Group( \u0022Gizmo Settings\u0022 )]\n\tpublic bool ShowLimitGizmo { get; set; } = true;\n\n    /// \u003Csummary\u003E\n    /// Keeps the limit gizmo visible even when the chain is not selected.\n    /// Useful for debugging multiple chains at once.\n    /// \u003C/summary\u003E\n\t[Property, Order( 29 ), Title( \u0022Always Show Gizmo\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowLimitGizmo ), true )]\n\tpublic bool AlwaysShowLimitGizmo { get; set; }\n\n    /// \u003Csummary\u003E\n    /// Color used for rendering the limit gizmo.\n    /// \u003C/summary\u003E\n\t[Property, Order( 30 ), Title( \u0022Limit Gizmo Color\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowLimitGizmo ), true )]\n\tpublic Color LimitGizmoColor { get; set; } = Color.Orange;\n\n    /// \u003Csummary\u003E\n    /// Controls the transparency of the limit gizmo.\n    /// \u003C/summary\u003E\n\t[Property, Order( 31 ), Range( 0, 1 ), Title( \u0022Gizmo Opacity\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowLimitGizmo ), true )]\n\tpublic float LimitGizmoOpacity { get; set; } = 1f;\n\n    /// \u003Csummary\u003E\n    /// Controls the size of the limit gizmo in the Scene view.\n    /// Adjust if the visualization appears too large or too small.\n    /// \u003C/summary\u003E\n\t[Property, Order( 32 ), Range( 0.1f, 1f ), Title( \u0022Limit Gizmo Size\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowLimitGizmo ), true )]\n\tpublic float LimitGizmoSize { get; set; } = 0.5f;\n\n    /// \u003Csummary\u003E\n    /// Displays the chain\u0027s collider in the Scene view.\n    /// Useful for visualizing collision shapes and sizes.\n    /// \u003C/summary\u003E\n\t[Property, Order( 33 ), Header( \u0022Collider Gizmo\u0022 ), Title( \u0022Collider Gizmo\u0022 ), Group( \u0022Gizmo Settings\u0022 )]\n\tpublic bool ShowColliderGizmo { get; set; } = true;\n\n    /// \u003Csummary\u003E\n    /// Keeps the collider gizmo visible even when the chain is not selected.\n    /// Useful for debugging collision setups.\n    /// \u003C/summary\u003E\n\t[Property, Order( 34 ), Title( \u0022Always Show Gizmo\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowColliderGizmo ), true )]\n\tpublic bool AlwaysShowGizmo { get; set; }\n\n    /// \u003Csummary\u003E\n    /// Color used for rendering the collider gizmo.\n    /// \u003C/summary\u003E\n\t[Property, Order( 35 ), Title( \u0022Collider Gizmo Color\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowColliderGizmo ), true )]\n\tpublic Color ColliderGizmoColor { get; set; } = Gizmo.Colors.Blue;\n\n    /// \u003Csummary\u003E\n    /// Controls the transparency of the collider gizmo.\n    /// \u003C/summary\u003E\n\t[Property, Order( 36 ), Range( 0, 1 ), Title( \u0022Gizmo Opacity\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowColliderGizmo ), true )]\n\tpublic float ColliderGizmoOpacity { get; set; } = 1f;\n\n//=========================================================================================================================\n// Private States\n//=========================================================================================================================\n\n\tprivate readonly List\u003CSoftBoneJointState\u003E _joints = new();\n\tprivate readonly List\u003CBoBiCoSoftBoneCollider\u003E _activeColliders = new();\n\tprivate readonly List\u003CSoftBoneColliderShape\u003E _externalBodyShapes = new();\n\tprivate readonly List\u003CSoftBoneColliderShape\u003E _externalChainShapes = new();\n\tprivate readonly List\u003CSoftBoneChainColliderSnapshot\u003E _publishedChainShapes = new();\n\tprivate readonly HashSet\u003CBoBiCoSoftBoneCollider\u003E _colliderSet = new();\n\tprivate readonly List\u003CTransform\u003E _framePose = new();\n\tprivate readonly List\u003CTransform\u003E _ragdollAnimationPose = new();\n\tprivate bool _dirty = true;\n\tprivate bool _usingPhysicsPose;\n\n\tpublic IReadOnlyList\u003CSoftBoneJointState\u003E Joints =\u003E _joints;\n\n\tpublic bool IsDirty =\u003E _dirty;\n\n\tpublic void MarkDirty() =\u003E _dirty = true;\n\n//=========================================================================================================================\n// Rebuild Section\n//=========================================================================================================================\n\n\tpublic bool Rebuild( SkinnedModelRenderer rendererOverride = null )\n\t{\n\t\tvar renderer = rendererOverride;\n\t\tif ( renderer is null )\n\t\t\treturn false;\n\n\t\tif ( !renderer.IsValid() || renderer.Model?.Bones is null )\n\t\t\treturn false;\n\n\t\tReleaseCurrentChain( renderer );\n\n\t\tvar boneName = \u0022\u0022;\n\t\tif ( TargetBone is not null \u0026\u0026 TargetBone.IsValid() )\n\t\t\tboneName = TargetBone.Name;\n\t\tboneName = boneName?.Trim() ?? \u0022\u0022;\n\n\t\tif ( string.IsNullOrWhiteSpace( boneName ) || !renderer.Model.Bones.HasBone( boneName ) )\n\t\t\treturn false;\n\n\t\tvar current = renderer.Model.Bones.GetBone( boneName );\n\n\t\twhile ( current is not null )\n\t\t{\n\t\t\tif ( !SoftBoneSkinnedModelUtility.TryGetBoneAnimationLocal( renderer, current, out var animated ) )\n\t\t\t\tbreak;\n\n\t\t\tvar poseVector = GetAnimatedBoneVectorLocal( renderer, current, animated );\n\t\t\tvar length = Math.Max( poseVector.Length, Epsilon );\n\t\t\tvar localPoseVector = animated.Rotation.Inverse * poseVector;\n\n\t\t\tvar rendererDownLocal = RendererDownLocal( renderer );\n\t\t\tvar gravityNormalLocal = animated.Rotation.Inverse * rendererDownLocal;\n\n\t\t\tvar joint = new SoftBoneJointState\n\t\t\t{\n\t\t\t\tBone = current,\n\t\t\t\tOriginalLocalGravityNormal = gravityNormalLocal,\n\t\t\t\tLocalPoseVector = localPoseVector,\n\t\t\t\tRestTransformLocal = animated,\n\t\t\t\tLength = length,\n\t\t\t\tPreviousBegin = animated.Position,\n\t\t\t\tPreviousEnd = animated.Position \u002B poseVector,\n\t\t\t\tPreviousAnimatedBegin = animated.Position,\n\t\t\t\tPreviousAnimatedRotation = animated.Rotation,\n\t\t\t\tPreviousSimulationRotation = animated.Rotation,\n\t\t\t\tImmobileEnd = SoftBoneSkinnedModelUtility.ToWorld( renderer, animated.Position \u002B poseVector ),\n\t\t\t\tPreviousEndWorld = SoftBoneSkinnedModelUtility.ToWorld( renderer, animated.Position \u002B poseVector ),\n\t\t\t\tPreviousVector = poseVector,\n\t\t\t\tPreviousVelocity = Vector3.Zero,\n\t\t\t\tHasHistory = false\n\t\t\t};\n\n\t\t\t_joints.Add( joint );\n\t\t\tif ( current.Children.Count == 0 )\n\t\t\t\tbreak;\n\n\t\t\tcurrent = current.Children[0];\n\t\t}\n\n\t\t_dirty = false;\n\t\treturn _joints.Count \u003E 0;\n\t}\n\n\tprivate void ReleaseCurrentChain( SkinnedModelRenderer renderer )\n\t{\n\t\tif ( renderer is not null \u0026\u0026 renderer.IsValid() \u0026\u0026 renderer.SceneModel.IsValid() )\n\t\t{\n\t\t\tforeach ( var joint in _joints )\n\t\t\t{\n\t\t\t\tif ( joint.Bone is not null \u0026\u0026 renderer.TryGetBoneTransformAnimation( joint.Bone, out var animatedWorld ) )\n\t\t\t\t{\n\t\t\t\t\tvar animatedLocal = renderer.WorldTransform.ToLocal( animatedWorld );\n\t\t\t\t\trenderer.SceneModel.SetBoneOverride( joint.Bone.Index, in animatedLocal );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t_joints.Clear();\n\t\t_framePose.Clear();\n\t\t_ragdollAnimationPose.Clear();\n\t\t_publishedChainShapes.Clear();\n\t\tSoftBoneCollisionRegistry.Remove( this );\n\t}\n\n\tprivate void UpdateOwnerName()\n\t{\n\t\tif ( GameObject is null || !GameObject.IsValid() )\n\t\t\treturn;\n\n\t\tvar desiredName = TargetBone is not null \u0026\u0026 TargetBone.IsValid()\n\t\t\t? TargetBone.Name?.Trim()\n\t\t\t: \u0022Empty Chain\u0022;\n\t\tif ( !string.IsNullOrWhiteSpace( desiredName ) \u0026\u0026 GameObject.Name != desiredName )\n\t\t\tGameObject.Name = desiredName;\n\t}\n\n//=========================================================================================================================\n// Simulation Section\n//=========================================================================================================================\n\n\tpublic void Simulate( SkinnedModelRenderer renderer, float frameRatio )\n\t{\n\t\t// Auto-rebuild if dirty or empty \u2014 chain is self-healing so the Group\n\t\t// doesn\u0027t need to orchestrate the Rebuild/Simulate order externally.\n\t\tif ( _dirty || _joints.Count == 0 )\n\t\t{\n\t\t\tif ( !Rebuild( renderer ) )\n\t\t\t\treturn;\n\t\t}\n\n\t\tif ( renderer is null || !renderer.IsValid() || !renderer.SceneModel.IsValid() )\n\t\t\treturn;\n\n\t\tvar flag = frameRatio \u003E= 1f;\n\t\tvar rendererDownLocal = RendererDownLocal( renderer );\n\t\tCollectActiveColliders();\n\t\tvar usePhysicsPose = IsPhysicsDrivingRenderer( renderer );\n\t\tif ( usePhysicsPose != _usingPhysicsPose )\n\t\t{\n\t\t\t_usingPhysicsPose = usePhysicsPose;\n\t\t\tforeach ( var joint in _joints ) joint.HasHistory = false;\n\t\t\tif ( usePhysicsPose \u0026\u0026 !CaptureRagdollAnimationPose( renderer ) )\n\t\t\t\treturn;\n\t\t\tif ( !usePhysicsPose ) _ragdollAnimationPose.Clear();\n\t\t}\n\n\t\tif ( !CaptureFramePose( renderer, usePhysicsPose ) )\n\t\t\treturn;\n\n\t\tvar hasParentEnd = false;\n\t\tvar parentEnd = Vector3.Zero;\n\t\tvar parentAnimatedRotation = Rotation.Identity;\n\t\tvar parentSimulationRotation = Rotation.Identity;\n\n\t\t// LimitChainLength: simulate only the last ChainLimit bones (tip-to-root).\n\t\t// Handled here instead of Rebuild so SimulateRoot logic stays tied to the\n\t\t// ACTUAL chain root (index 0), not whichever bone happens to be first after slicing.\n\t\tvar startIndex = GetSimulationStartIndex();\n\t\tResetExcludedJointsToPose( renderer, startIndex );\n\n\t\tfor ( var i = startIndex; i \u003C _joints.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar joint = _joints[i];\n\t\t\tvar chainPosition = GetChainPosition( i );\n\t\t\tvar pull = EvaluateUnit( Pull, ControlMode, PullRange, PullCurve, chainPosition );\n\t\t\tvar spring = EvaluateUnit( Spring, ControlMode, SpringRange, SpringCurve, chainPosition );\n\t\t\tvar stiffness = EvaluateUnit( Stiffness, ControlMode, StiffnessRange, StiffnessCurve, chainPosition );\n\t\t\tvar immobile = EvaluateUnit( Immobile, ControlMode, ImmobileRange, ImmobileCurve, chainPosition );\n\t\t\tvar stretch = EvaluateNonNegative( Stretch, ControlMode, StretchRange, StretchCurve, chainPosition );\n\t\t\tvar squish = EvaluateNonNegative( Squish, ControlMode, SquishRange, SquishCurve, chainPosition );\n\t\t\tvar gravity = EvaluateSignedUnit( Gravity, ControlMode, GravityRange, GravityCurve, chainPosition );\n\t\t\tvar gravityFalloff = EvaluateUnit( GravityFalloff, ControlMode, GravityFalloffRange, GravityFalloffCurve, chainPosition );\n\t\t\tvar colliderRadius = EvaluateColliderRadius( chainPosition );\n\t\t\tvar maxAngle = EvaluateAngle( MaxAngle, ControlMode, MaxAngleRange, MaxAngleCurve, chainPosition );\n\t\t\tvar maxYaw = EvaluateAngle( MaxYaw, ControlMode, MaxYawRange, MaxYawCurve, chainPosition );\n\t\t\tvar maxPitch = EvaluateAngle( MaxPitch, ControlMode, MaxPitchRange, MaxPitchCurve, chainPosition );\n\t\t\tvar isActualRoot = i == 0;\n\t\t\tvar isSimulationRoot = i == startIndex;\n\t\t\tvar isRootSimulated = isActualRoot \u0026\u0026 SimulateRoot;\n\t\t\tvar animated = _framePose[i];\n\n\t\t\tvar begin = hasParentEnd ? parentEnd : animated.Position;\n\n\t\t\tvar animatedPoseVector = GetFramePoseVector( i, joint, animated );\n\t\t\tvar parentDeflection = isSimulationRoot ? Rotation.Identity : GetParentDeflection( i, parentAnimatedRotation, parentSimulationRotation );\n\t\t\tvar simulationRotation = parentDeflection * animated.Rotation;\n\t\t\tvar poseVector = parentDeflection * animatedPoseVector;\n\t\t\tif ( isSimulationRoot \u0026\u0026 RotationOffset != default )\n\t\t\t{\n\t\t\t\tvar offsetRotation = new Angles( RotationOffset.yaw, RotationOffset.pitch, RotationOffset.roll ).ToRotation();\n\t\t\t\tsimulationRotation = animated.Rotation * offsetRotation;\n\t\t\t\tposeVector = simulationRotation * (animated.Rotation.Inverse * animatedPoseVector);\n\t\t\t}\n\n\t\t\tvar restLength = Math.Max( poseVector.Length, Epsilon );\n\t\t\tvar minLength = Math.Max( 0f, restLength * (1f - squish) );\n\t\t\tvar maxLength = Math.Max( minLength, restLength * (1f \u002B stretch) );\n\n\t\t\tvar endPoint = joint.HasHistory\n\t\t\t\t? renderer.WorldTransform.PointToLocal( joint.PreviousEndWorld )\n\t\t\t\t: begin \u002B poseVector;\n\n\t\t\tif ( joint.HasHistory )\n\t\t\t{\n\t\t\t\tvar rootDelta = begin - joint.PreviousAnimatedBegin;\n\t\t\t\tendPoint \u002B= rootDelta;\n\n\t\t\t\tvar rotationDelta = animated.Rotation * joint.PreviousAnimatedRotation.Inverse;\n\t\t\t\tendPoint = begin \u002B rotationDelta * (endPoint - begin);\n\t\t\t}\n\n\t\t\t// SimulateRoot=false: only skip physics on the TRUE chain root, not on bones that\n\t\t\t// happen to be first because LimitChainLength trimmed the start of the iteration.\n\t\t\tif ( isActualRoot \u0026\u0026 !isRootSimulated \u0026\u0026 !CollideWithRoot )\n\t\t\t{\n\t\t\t\tResetToRestPose( renderer, joint, animated, begin, poseVector, simulationRotation );\n\t\t\t\tparentEnd = joint.PreviousEnd;\n\t\t\t\tparentAnimatedRotation = animated.Rotation;\n\t\t\t\tparentSimulationRotation = joint.PreviousSimulationRotation;\n\t\t\t\thasParentEnd = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ( pull \u003C= Epsilon )\n\t\t\t{\n\t\t\t\tResetToRestPose( renderer, joint, animated, begin, poseVector, simulationRotation );\n\t\t\t\tif ( !isActualRoot )\n\t\t\t\t\tApplySolvedTransform( renderer, joint, animated, simulationRotation );\n\t\t\t\tparentEnd = joint.PreviousEnd;\n\t\t\t\tparentAnimatedRotation = animated.Rotation;\n\t\t\t\tparentSimulationRotation = joint.PreviousSimulationRotation;\n\t\t\t\thasParentEnd = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar previousEndPoint = endPoint;\n\n\t\t\tif ( joint.HasHistory \u0026\u0026 immobile \u003E Epsilon )\n\t\t\t{\n\t\t\t\tvar immobileTarget = renderer.WorldTransform.PointToLocal( joint.ImmobileEnd );\n\t\t\t\tendPoint \u002B= (immobileTarget - endPoint) * immobile;\n\t\t\t}\n\n\t\t\tvar gravityAmount = GetGravityAmount( joint, animated, rendererDownLocal, gravity, gravityFalloff );\n\t\t\tvar gravityTargetEnd = ApplyGravityTarget( begin, poseVector, restLength, gravityAmount, gravity, rendererDownLocal );\n\n\t\t\tvar solvedEndPoint = BoBiCoSoftBoneSolver.IntegrateAdvanced(\n\t\t\t\tbegin, endPoint, previousEndPoint,\n\t\t\t\tposeVector, joint.PreviousVector, joint.PreviousVelocity,\n\t\t\t\tminLength, maxLength, pull, spring, stiffness, gravityTargetEnd, frameRatio,\n\t\t\t\tout var newVelocity );\n\n\t\t\tvar solvedDirection = BoBiCoSoftBoneSolver.SafeNormal( solvedEndPoint - begin, poseVector );\n\t\t\tsolvedDirection = SoftBoneAngleLimit.Apply( poseVector, solvedDirection, LimitType, maxAngle, maxYaw, maxPitch, LimitRotation );\n\t\t\tvar solvedLength = Math.Clamp( (solvedEndPoint - begin).Length, minLength, maxLength );\n\t\t\tvar solvedEnd = begin \u002B solvedDirection * solvedLength;\n\t\t\tsolvedEnd = ResolveCollisions( renderer, begin, solvedEnd, poseVector, minLength, maxLength, colliderRadius, maxAngle, maxYaw, maxPitch, isActualRoot \u0026\u0026 CollideWithRoot, out var wasColliding );\n\t\t\tsolvedDirection = BoBiCoSoftBoneSolver.SafeNormal( solvedEnd - begin, poseVector );\n\t\t\tnewVelocity = solvedEnd - previousEndPoint;\n\t\t\tif ( wasColliding ) newVelocity *= 0.75f;\n\n\t\t\tif ( flag )\n\t\t\t{\n\t\t\t\tjoint.PreviousEndWorld = SoftBoneSkinnedModelUtility.ToWorld( renderer, solvedEnd );\n\t\t\t\tjoint.ImmobileEnd = joint.PreviousEndWorld;\n\t\t\t\tjoint.PreviousVector = solvedEnd - begin;\n\t\t\t\tjoint.PreviousVelocity = newVelocity;\n\t\t\t\tjoint.PreviousAnimatedBegin = begin;\n\t\t\t\tjoint.PreviousAnimatedRotation = animated.Rotation;\n\t\t\t}\n\n\t\t\tjoint.PreviousBegin = begin;\n\t\t\tjoint.PreviousEnd = solvedEnd;\n\t\t\tjoint.PreviousSimulationRotation = BoBiCoSoftBoneSolver.SolveHeadRotation( poseVector, solvedEnd - begin, simulationRotation );\n\t\t\tjoint.HasHistory = true;\n\n\t\t\tif ( !isActualRoot )\n\t\t\t\tApplySolvedTransform( renderer, joint, animated, joint.PreviousSimulationRotation );\n\n\t\t\tparentEnd = solvedEnd;\n\t\t\tparentAnimatedRotation = animated.Rotation;\n\t\t\tparentSimulationRotation = joint.PreviousSimulationRotation;\n\t\t\thasParentEnd = true;\n\t\t}\n\n\t\tPublishCollisionShapes( renderer );\n\t}\n\n\tprivate void CollectActiveColliders()\n\t{\n\t\t_activeColliders.Clear();\n\t\t_colliderSet.Clear();\n\n\t\tif ( ColliderGroups is not null )\n\t\t{\n\t\t\tforeach ( var sourceObject in ColliderGroups )\n\t\t\t{\n\t\t\t\tif ( sourceObject is null || !sourceObject.IsValid() )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tsourceObject.GetComponent\u003CBoBiCoSoftBoneColliderGroup\u003E()?.CollectColliders( _activeColliders, _colliderSet );\n\t\t\t}\n\t\t}\n\n\t}\n\n\tprivate void ResetExcludedJointsToPose( SkinnedModelRenderer renderer, int startIndex )\n\t{\n\t\tfor ( var i = 0; i \u003C startIndex; i\u002B\u002B )\n\t\t{\n\t\t\tvar joint = _joints[i];\n\t\t\tvar animated = _framePose[i];\n\t\t\tvar poseVector = GetFramePoseVector( i, joint, animated );\n\t\t\tResetToRestPose( renderer, joint, animated, animated.Position, poseVector, animated.Rotation );\n\t\t\tif ( i \u003E 0 )\n\t\t\t\tApplyAnimationTransform( renderer, joint, animated );\n\t\t}\n\t}\n\n\tprivate Vector3 ResolveCollisions( SkinnedModelRenderer renderer, Vector3 begin, Vector3 solvedEnd, Vector3 poseVector, float minLength, float maxLength, float colliderRadius, float maxAngle, float maxYaw, float maxPitch, bool includeRootHead, out bool collided )\n\t{\n\t\tcollided = false;\n\t\tif ( CollisionMode == SoftBoneCollisionMode.None || colliderRadius \u003C= Epsilon )\n\t\t\treturn solvedEnd;\n\n\t\tvar worldBegin = SoftBoneSkinnedModelUtility.ToWorld( renderer, begin );\n\t\tvar worldEnd = SoftBoneSkinnedModelUtility.ToWorld( renderer, solvedEnd );\n\t\tforeach ( var collider in _activeColliders )\n\t\t{\n\t\t\tif ( collider.TryGetWorldShape( collider.GetOwningRenderer(), out var shape ) )\n\t\t\t\tcollided |= SoftBoneCollisionSolver.Resolve( ref worldEnd, worldBegin, includeRootHead ? worldBegin : GetCollisionStart( worldBegin, worldEnd, colliderRadius ), colliderRadius, minLength, maxLength, shape );\n\t\t}\n\n\t\tif ( CollisionMode == SoftBoneCollisionMode.FullCollision )\n\t\t{\n\t\t\t_externalBodyShapes.Clear();\n\t\t\tSoftBoneCollisionRegistry.CollectExternalBodyShapes( renderer, _externalBodyShapes );\n\t\t\tforeach ( var shape in _externalBodyShapes )\n\t\t\t\tcollided |= SoftBoneCollisionSolver.Resolve( ref worldEnd, worldBegin, includeRootHead ? worldBegin : GetCollisionStart( worldBegin, worldEnd, colliderRadius ), colliderRadius, minLength, maxLength, shape );\n\t\t}\n\n\t\t_externalChainShapes.Clear();\n\t\tSoftBoneCollisionRegistry.CollectChainShapes( this, renderer, CollisionMode, _externalChainShapes );\n\t\tforeach ( var shape in _externalChainShapes )\n\t\t\tcollided |= SoftBoneCollisionSolver.Resolve( ref worldEnd, worldBegin, includeRootHead ? worldBegin : GetCollisionStart( worldBegin, worldEnd, colliderRadius ), colliderRadius, minLength, maxLength, shape );\n\n\t\tif ( !collided )\n\t\t\treturn solvedEnd;\n\n\t\tvar localDelta = SoftBoneSkinnedModelUtility.ToLocal( renderer, worldEnd ) - begin;\n\t\tvar localDirection = SoftBoneAngleLimit.Apply( poseVector, BoBiCoSoftBoneSolver.SafeNormal( localDelta, poseVector ), LimitType, maxAngle, maxYaw, maxPitch, LimitRotation );\n\t\treturn begin \u002B localDirection * Math.Clamp( localDelta.Length, minLength, maxLength );\n\t}\n\n\tprivate Vector3 GetCollisionStart( Vector3 worldBegin, Vector3 worldEnd, float colliderRadius )\n\t{\n\t\tif ( ColliderShape != SoftBoneLinkColliderShape.Capsule )\n\t\t\treturn worldEnd;\n\n\t\tvar axis = worldEnd - worldBegin;\n\t\tvar segmentLength = Math.Max( 0f, ColliderHeight - colliderRadius * 2f );\n\t\tif ( axis.Length \u003C= Epsilon || segmentLength \u003C= Epsilon )\n\t\t\treturn worldEnd;\n\n\t\treturn worldEnd - axis.Normal * Math.Min( axis.Length, segmentLength );\n\t}\n\n\tprivate int GetSimulationStartIndex()\n\t{\n\t\treturn LimitChainLength \u0026\u0026 ChainLimit \u003E 0 \u0026\u0026 ChainLimit \u003C _joints.Count\n\t\t\t? _joints.Count - ChainLimit\n\t\t\t: 0;\n\t}\n\n\tprivate float GetChainPosition( int index ) =\u003E _joints.Count \u003C= 1 ? 0f : index / (float)(_joints.Count - 1);\n\tprivate static Curve ConstantCurve( float value ) =\u003E new( new Curve.Frame( 0f, value ), new Curve.Frame( 1f, value ) );\n\n\tprivate void InitializeCurveDefaults()\n\t{\n\t\t_curveDefaultsInitialized = true;\n\t\tPullCurve = ConstantCurve( Pull );\n\t\tSpringCurve = ConstantCurve( Spring );\n\t\tStiffnessCurve = ConstantCurve( Stiffness );\n\t\tImmobileCurve = ConstantCurve( Immobile );\n\t\tStretchCurve = ConstantCurve( Stretch );\n\t\tSquishCurve = ConstantCurve( Squish );\n\t\tGravityCurve = ConstantCurve( Gravity );\n\t\tGravityFalloffCurve = ConstantCurve( GravityFalloff );\n\t\tMaxAngleCurve = ConstantCurve( MaxAngle / 180f );\n\t\tMaxYawCurve = ConstantCurve( MaxYaw / 180f );\n\t\tMaxPitchCurve = ConstantCurve( MaxPitch / 180f );\n\t\tColliderRadiusCurve = ConstantCurve( ColliderRadius );\n\t}\n\n\tprivate static float EvaluateUnit( float value, SoftBoneControlMode mode, Vector2 range, Curve curve, float position ) =\u003E Math.Clamp( EvaluateControl( value, mode, range, curve, position ), 0f, 1f );\n\tprivate static float EvaluateSignedUnit( float value, SoftBoneControlMode mode, Vector2 range, Curve curve, float position ) =\u003E Math.Clamp( EvaluateControl( value, mode, range, curve, position ), -1f, 1f );\n\tprivate static float EvaluateNonNegative( float value, SoftBoneControlMode mode, Vector2 range, Curve curve, float position ) =\u003E Math.Max( 0f, EvaluateControl( value, mode, range, curve, position ) );\n\tprivate static float EvaluateAngle( float value, SoftBoneControlMode mode, Vector2 range, Curve curve, float position )\n\t{\n\t\tif ( mode == SoftBoneControlMode.Curve )\n\t\t\treturn Math.Clamp( (curve.Length \u003E 0 ? curve.Evaluate( position ) : 1f) * 180f, 0f, 180f );\n\n\t\treturn Math.Clamp( EvaluateControl( value, mode, range, curve, position ), 0f, 180f );\n\t}\n\tprivate float EvaluateColliderRadius( float position ) =\u003E Math.Max( 0.001f, EvaluateControl( ColliderRadius, ControlMode, ColliderRadiusRange, ColliderRadiusCurve, position ) );\n\tprivate static float EvaluateControl( float value, SoftBoneControlMode mode, Vector2 range, Curve curve, float position )\n\t{\n\t\treturn mode switch\n\t\t{\n\t\t\tSoftBoneControlMode.Range =\u003E range.x \u002B (range.y - range.x) * position,\n\t\t\tSoftBoneControlMode.Curve =\u003E curve.Length \u003E 0 ? curve.Evaluate( position ) : value,\n\t\t\t_ =\u003E value\n\t\t};\n\t}\n\n\tinternal void PublishCollisionShapes( SkinnedModelRenderer renderer )\n\t{\n\t\tif ( CollisionMode == SoftBoneCollisionMode.None )\n\t\t{\n\t\t\tSoftBoneCollisionRegistry.Remove( this );\n\t\t\treturn;\n\t\t}\n\n\t\t_publishedChainShapes.Clear();\n\t\tfor ( var i = GetSimulationStartIndex(); i \u003C _joints.Count; i\u002B\u002B )\n\t\t{\n\t\t\tif ( i == 0 \u0026\u0026 !CollideWithRoot )\n\t\t\t\tcontinue;\n\n\t\t\tvar joint = _joints[i];\n\t\t\tif ( !joint.HasHistory )\n\t\t\t\tcontinue;\n\n\t\t\tvar begin = SoftBoneSkinnedModelUtility.ToWorld( renderer, joint.PreviousBegin );\n\t\t\tvar end = SoftBoneSkinnedModelUtility.ToWorld( renderer, joint.PreviousEnd );\n\t\t\tvar radius = EvaluateColliderRadius( GetChainPosition( i ) );\n\t\t\tvar direction = BoBiCoSoftBoneSolver.SafeNormal( end - begin, Vector3.Forward );\n\t\t\tSoftBoneColliderShape shape;\n\t\t\tswitch ( ColliderShape )\n\t\t\t{\n\t\t\t\tcase SoftBoneLinkColliderShape.Sphere:\n\t\t\t\t\tshape = SoftBoneColliderShape.Sphere( begin, radius );\n\t\t\t\t\tbreak;\n\t\t\t\tcase SoftBoneLinkColliderShape.Box:\n\t\t\t\t\tshape = SoftBoneColliderShape.Box( begin, Rotation.FromToRotation( Vector3.Forward, direction ), new Vector3( BoxDimension.z, BoxDimension.x, BoxDimension.y ) );\n\t\t\t\t\tbreak;\n\t\t\t\tcase SoftBoneLinkColliderShape.Flat:\n\t\t\t\t\tshape = SoftBoneColliderShape.Flat( begin, direction );\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tvar halfLine = Math.Max( 0f, ColliderHeight * 0.5f - radius );\n\t\t\t\t\tshape = SoftBoneColliderShape.Capsule( begin - direction * halfLine, begin \u002B direction * halfLine, radius );\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t_publishedChainShapes.Add( new SoftBoneChainColliderSnapshot( renderer, shape ) );\n\t\t}\n\n\t\tSoftBoneCollisionRegistry.Publish( this, renderer, _publishedChainShapes );\n\t}\n\n\n//=========================================================================================================================\n// Helpers\n//=========================================================================================================================\n\n\tprivate bool CaptureFramePose( SkinnedModelRenderer renderer, bool usePhysicsPose )\n\t{\n\t\t_framePose.Clear();\n\n\t\tTransform rootWorld = default;\n\t\tif ( !renderer.TryGetBoneTransform( _joints[0].Bone, out rootWorld )\n\t\t\t\u0026\u0026 !renderer.TryGetBoneTransformAnimation( _joints[0].Bone, out rootWorld ) )\n\t\t\treturn false;\n\t\tvar rootPose = renderer.WorldTransform.ToLocal( rootWorld );\n\t\t_framePose.Add( rootPose );\n\n\t\tfor ( var i = 1; i \u003C _joints.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar joint = _joints[i];\n\t\t\tTransform animationPose;\n\t\t\tif ( usePhysicsPose )\n\t\t\t{\n\t\t\t\tif ( _ragdollAnimationPose.Count != _joints.Count ) return false;\n\t\t\t\tanimationPose = ApplyRootPoseDelta( _ragdollAnimationPose[i], _ragdollAnimationPose[0], rootPose );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif ( !renderer.TryGetBoneTransformAnimation( joint.Bone, out var worldTransform ) )\n\t\t\t\t\treturn false;\n\t\t\t\tanimationPose = renderer.WorldTransform.ToLocal( worldTransform );\n\t\t\t}\n\n\t\t\t_framePose.Add( animationPose );\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tprivate bool CaptureRagdollAnimationPose( SkinnedModelRenderer renderer )\n\t{\n\t\t_ragdollAnimationPose.Clear();\n\t\tforeach ( var joint in _joints )\n\t\t{\n\t\t\tif ( !renderer.TryGetBoneTransformAnimation( joint.Bone, out var worldTransform ) )\n\t\t\t{\n\t\t\t\t_ragdollAnimationPose.Clear();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t_ragdollAnimationPose.Add( renderer.WorldTransform.ToLocal( worldTransform ) );\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tprivate static Transform ApplyRootPoseDelta( Transform pose, Transform animationRoot, Transform finalRoot )\n\t{\n\t\tvar rotationDelta = finalRoot.Rotation * animationRoot.Rotation.Inverse;\n\t\tvar positionDelta = finalRoot.Position - rotationDelta * animationRoot.Position;\n\t\treturn new Transform( positionDelta \u002B rotationDelta * pose.Position, rotationDelta * pose.Rotation, pose.Scale );\n\t}\n\n\tprivate bool IsPhysicsDrivingRenderer( SkinnedModelRenderer renderer )\n\t{\n\t\tif ( Scene is null ) return false;\n\t\tforeach ( var physics in Scene.GetAllComponents\u003CModelPhysics\u003E() )\n\t\t\tif ( physics is not null \u0026\u0026 physics.Enabled \u0026\u0026 physics.MotionEnabled \u0026\u0026 physics.Renderer == renderer )\n\t\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\tprivate bool TryGetSolveTransformLocal( SkinnedModelRenderer renderer, SoftBoneJointState joint, out Transform transform )\n\t{\n\t\tif ( renderer is not null \u0026\u0026 renderer.IsValid() \u0026\u0026 joint.Bone is not null\n\t\t\t\u0026\u0026 renderer.TryGetBoneTransformAnimation( joint.Bone, out var worldTransform ) )\n\t\t{\n\t\t\ttransform = renderer.WorldTransform.ToLocal( worldTransform );\n\t\t\treturn true;\n\t\t}\n\n\t\ttransform = joint.RestTransformLocal;\n\t\treturn joint.RestTransformLocal.Scale != Vector3.Zero;\n\t}\n\n\tprivate static Vector3 GetAnimatedBoneVectorLocal( SkinnedModelRenderer renderer, BoneCollection.Bone bone, Transform animated )\n\t{\n\t\tif ( bone.Children.Count \u003E 0 \u0026\u0026 SoftBoneSkinnedModelUtility.TryGetBoneAnimationLocal( renderer, bone.Children[0], out var child ) )\n\t\t{\n\t\t\tvar direction = child.Position - animated.Position;\n\t\t\tif ( direction.Length \u003E Epsilon )\n\t\t\t\treturn direction;\n\t\t}\n\n\t\tvar fallback = animated.Rotation * Vector3.Forward;\n\t\treturn fallback.Length \u003E Epsilon ? fallback : Vector3.Forward;\n\t}\n\n\tprivate Vector3 GetFramePoseVector( int index, SoftBoneJointState joint, Transform pose )\n\t{\n\t\tif ( index \u002B 1 \u003C _framePose.Count )\n\t\t{\n\t\t\tvar direction = _framePose[index \u002B 1].Position - pose.Position;\n\t\t\tif ( direction.Length \u003E Epsilon ) return direction;\n\t\t}\n\n\t\treturn GetPoseVectorLocal( joint, pose );\n\t}\n\n\tprivate static Vector3 GetPoseVectorLocal( SoftBoneJointState joint, Transform animated )\n\t{\n\t\tvar poseVector = animated.Rotation * joint.LocalPoseVector;\n\t\tif ( poseVector.Length \u003E Epsilon )\n\t\t\treturn poseVector;\n\n\t\tvar fallback = animated.Rotation * Vector3.Forward;\n\t\treturn fallback.Length \u003E Epsilon ? fallback : Vector3.Forward;\n\t}\n\n\tprivate static Rotation GetParentDeflection( int index, Rotation parentAnimatedRotation, Rotation parentSimulationRotation )\n\t{\n\t\tif ( index \u003C= 0 )\n\t\t\treturn Rotation.Identity;\n\n\t\treturn parentSimulationRotation * parentAnimatedRotation.Inverse;\n\t}\n\n\tprivate float GetGravityAmount( SoftBoneJointState joint, Transform animated, Vector3 rendererDownLocal, float gravity, float gravityFalloff )\n\t{\n\t\tif ( Math.Abs( gravity ) \u003C= Epsilon )\n\t\t\treturn 0f;\n\n\t\tvar gravityAmount = Math.Abs( gravity );\n\t\tif ( gravityFalloff \u003E Epsilon )\n\t\t{\n\t\t\tvar gravityNormal = animated.Rotation * joint.OriginalLocalGravityNormal;\n\t\t\tvar gravityBias = Math.Min( 1f, 1f - Vector3.Dot(\n\t\t\t\tBoBiCoSoftBoneSolver.SafeNormal( gravityNormal, rendererDownLocal ),\n\t\t\t\trendererDownLocal ) );\n\t\t\tgravityAmount *= (1f - gravityFalloff) \u002B gravityFalloff * gravityBias;\n\t\t}\n\n\t\treturn Math.Clamp( gravityAmount, 0f, 1f );\n\t}\n\n\tprivate static Vector3 ApplyGravityTarget( Vector3 begin, Vector3 poseVector, float restLength, float gravityAmount, float gravity, Vector3 rendererDownLocal )\n\t{\n\t\tif ( gravityAmount \u003C= Epsilon )\n\t\t\treturn begin \u002B poseVector;\n\n\t\tvar gravityVector = (gravity \u003E 0f ? rendererDownLocal : -rendererDownLocal) * restLength;\n\t\tvar targetVector = poseVector \u002B (gravityVector - poseVector) * gravityAmount;\n\t\treturn begin \u002B BoBiCoSoftBoneSolver.SafeNormal( targetVector, poseVector ) * restLength;\n\t}\n\n\tprivate static void ResetToRestPose( SkinnedModelRenderer renderer, SoftBoneJointState joint, Transform animated, Vector3 begin, Vector3 poseVector, Rotation simulationRotation )\n\t{\n\t\tvar restEnd = begin \u002B poseVector;\n\t\tjoint.PreviousBegin = begin;\n\t\tjoint.PreviousEnd = restEnd;\n\t\tjoint.PreviousAnimatedBegin = begin;\n\t\tjoint.PreviousAnimatedRotation = animated.Rotation;\n\t\tjoint.PreviousSimulationRotation = simulationRotation;\n\t\tjoint.ImmobileEnd = SoftBoneSkinnedModelUtility.ToWorld( renderer, restEnd );\n\t\tjoint.PreviousEndWorld = joint.ImmobileEnd;\n\t\tjoint.PreviousVector = poseVector;\n\t\tjoint.PreviousVelocity = Vector3.Zero;\n\t\tjoint.HasHistory = true;\n\t}\n\n\tprivate static void ApplySolvedTransform( SkinnedModelRenderer renderer, SoftBoneJointState joint, Transform animated, Rotation solvedRotation )\n\t{\n\t\tvar localTransform = new Transform( joint.PreviousBegin, solvedRotation, animated.Scale );\n\t\trenderer.SceneModel.SetBoneOverride( joint.Bone.Index, in localTransform );\n\t}\n\n\tprivate static void ApplyAnimationTransform( SkinnedModelRenderer renderer, SoftBoneJointState joint, Transform animated )\n\t{\n\t\trenderer.SceneModel.SetBoneOverride( joint.Bone.Index, in animated );\n\t}\n\n\tprivate static Vector3 RendererDownLocal( SkinnedModelRenderer renderer )\n\t{\n\t\tif ( renderer is null || !renderer.IsValid() )\n\t\t\treturn Vector3.Down;\n\n\t\treturn BoBiCoSoftBoneSolver.SafeNormal( renderer.WorldTransform.Rotation.Inverse * Vector3.Down, Vector3.Down );\n\t}\n\n//=========================================================================================================================\n// Building Gizmo \n//=========================================================================================================================\n\n\tprotected override void DrawGizmos()\n\t{\n\t\tbase.DrawGizmos();\n\t\tvar drawCollider = CollisionMode != SoftBoneCollisionMode.None \u0026\u0026 ShowColliderGizmo \u0026\u0026 (AlwaysShowGizmo || Gizmo.IsSelected);\n\t\tvar drawLimit = ShowLimitGizmo \u0026\u0026 (AlwaysShowLimitGizmo || Gizmo.IsSelected) \u0026\u0026 LimitType != SoftBoneLimitType.None;\n\t\tif ( !drawCollider \u0026\u0026 !drawLimit )\n\t\t\treturn;\n\n\t\tvar renderer = FindRendererForGizmo();\n\t\tif ( renderer is null || !renderer.IsValid() )\n\t\t\treturn;\n\n\t\tif ( _dirty || _joints.Count == 0 )\n\t\t\tRebuild( renderer );\n\n\t\tusing ( Transform.DisableProxy() )\n\t\t{\n\t\t\tGizmo.Transform = new Transform( Vector3.Zero, Rotation.Identity, 1f );\n\t\t\tGizmo.Draw.IgnoreDepth = true;\n\t\t\tGizmo.Draw.LineThickness = 1;\n\t\t\tfor ( var i = GetSimulationStartIndex(); i \u003C _joints.Count; i\u002B\u002B )\n\t\t\t{\n\t\t\t\tvar joint = _joints[i];\n\t\t\t\tif ( !TryGetSolveTransformLocal( renderer, joint, out var animated ) )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tvar solvedBegin = joint.HasHistory ? joint.PreviousBegin : animated.Position;\n\t\t\t\tvar solvedEnd = joint.HasHistory ? joint.PreviousEnd : animated.Position \u002B GetPoseVectorLocal( joint, animated );\n\t\t\t\tvar physicsBegin = SoftBoneSkinnedModelUtility.ToWorld( renderer, solvedBegin );\n\t\t\t\tvar boneObject = renderer.GetBoneObject( joint.Bone );\n\t\t\t\tvar begin = boneObject is not null \u0026\u0026 boneObject.IsValid()\n\t\t\t\t\t? boneObject.WorldPosition\n\t\t\t\t\t: physicsBegin;\n\t\t\t\tvar end = SoftBoneSkinnedModelUtility.ToWorld( renderer, solvedEnd );\n\t\t\t\t// Root bone: only draw when SimulateRoot is enabled.\n\t\t\t\t// Non-root bones: skip leaf/endpoint bones (Children.Count == 0) since their\n\t\t\t\t// endpoint is a synthetic forward poseVector, not a real child position.\n\t\t\t\t// The Children.Count gate must NOT apply to the simulated root \u2014 it may be a\n\t\t\t\t// leaf or near-leaf bone and still needs collision when SimulateRoot is on.\n\t\t\t\tvar drawJointCollider = drawCollider\n\t\t\t\t\t\u0026\u0026 (i != 0 || (SimulateRoot \u0026\u0026 CollideWithRoot));\n\t\t\t\tif ( drawJointCollider )\n\t\t\t\t{\n\t\t\t\t\tGizmo.Draw.Color = ColliderGizmoColor.WithAlpha( ColliderGizmoOpacity );\n\t\t\t\t\tDrawLinkColliderGizmo( begin, end, EvaluateColliderRadius( GetChainPosition( i ) ) );\n\t\t\t\t}\n\n\t\t\t\tvar drawJointLimit = drawLimit \u0026\u0026 (i != 0 || SimulateRoot);\n\t\t\t\tif ( drawJointLimit )\n\t\t\t\t{\n\t\t\t\t\tvar solvedVector = solvedEnd - solvedBegin;\n\t\t\t\t\tvar chainPosition = GetChainPosition( i );\n\t\t\t\t\tvar colliderRadius = EvaluateColliderRadius( chainPosition );\n\t\t\t\t\tDrawLimitGizmo( renderer, physicsBegin, solvedVector, Math.Max( joint.Length, colliderRadius * 2f ) * LimitGizmoSize, EvaluateAngle( MaxAngle, ControlMode, MaxAngleRange, MaxAngleCurve, chainPosition ), EvaluateAngle( MaxYaw, ControlMode, MaxYawRange, MaxYawCurve, chainPosition ), EvaluateAngle( MaxPitch, ControlMode, MaxPitchRange, MaxPitchCurve, chainPosition ) );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void DrawLinkColliderGizmo( Vector3 worldStart, Vector3 worldEnd, float colliderRadius )\n\t{\n\t\tif ( ColliderShape == SoftBoneLinkColliderShape.Sphere )\n\t\t{\n\t\t\tGizmo.Draw.LineSphere( new Sphere( worldStart, colliderRadius ) );\n\t\t\treturn;\n\t\t}\n\t\tif ( ColliderShape == SoftBoneLinkColliderShape.Capsule )\n\t\t{\n\t\t\tvar direction = BoBiCoSoftBoneSolver.SafeNormal( worldEnd - worldStart, Vector3.Forward );\n\t\t\tvar halfLine = Math.Max( 0f, ColliderHeight * 0.5f - colliderRadius );\n\t\t\tGizmo.Draw.LineCapsule( new Capsule( worldStart - direction * halfLine, worldStart \u002B direction * halfLine, colliderRadius ) );\n\t\t\treturn;\n\t\t}\n\n\t\tvar forward = BoBiCoSoftBoneSolver.SafeNormal( worldEnd - worldStart, Vector3.Forward );\n\t\tvar right = BoBiCoSoftBoneSolver.SafeNormal( Vector3.Cross( forward, Vector3.Up ), Vector3.Right );\n\t\tvar up = BoBiCoSoftBoneSolver.SafeNormal( Vector3.Cross( right, forward ), Vector3.Up );\n\t\tif ( ColliderShape == SoftBoneLinkColliderShape.Flat )\n\t\t{\n\t\t\tvar half = colliderRadius;\n\t\t\tvar a = worldStart \u002B (right \u002B up) * half; var b = worldStart \u002B (right - up) * half;\n\t\t\tvar c = worldStart \u002B (-right - up) * half; var d = worldStart \u002B (-right \u002B up) * half;\n\t\t\tGizmo.Draw.Line( a, b ); Gizmo.Draw.Line( b, c ); Gizmo.Draw.Line( c, d ); Gizmo.Draw.Line( d, a );\n\t\t\tGizmo.Draw.Line( worldStart, worldStart \u002B forward * Math.Max( colliderRadius, 0.2f ) );\n\t\t\treturn;\n\t\t}\n\n\t\tvar halfSize = BoxDimension * 0.5f;\n\t\tvar corners = new Vector3[8];\n\t\tfor ( var corner = 0; corner \u003C 8; corner\u002B\u002B )\n\t\t\tcorners[corner] = worldStart\n\t\t\t\t\u002B forward * (((corner \u0026 1) == 0 ? -1f : 1f) * halfSize.z)\n\t\t\t\t\u002B right   * (((corner \u0026 2) == 0 ? -1f : 1f) * halfSize.x)\n\t\t\t\t\u002B up      * (((corner \u0026 4) == 0 ? -1f : 1f) * halfSize.y);\n\t\tint[] edges = { 0, 1, 0, 2, 0, 4, 1, 3, 1, 5, 2, 3, 2, 6, 3, 7, 4, 5, 4, 6, 5, 7, 6, 7 };\n\t\tfor ( var edge = 0; edge \u003C edges.Length; edge \u002B= 2 ) Gizmo.Draw.Line( corners[edges[edge]], corners[edges[edge \u002B 1]] );\n\t}\n\n\tprivate void DrawLimitGizmo( SkinnedModelRenderer renderer, Vector3 worldBegin, Vector3 solvedVector, float radius, float maxAngle, float maxYaw, float maxPitch )\n\t{\n\t\tSoftBoneAngleLimit.GetAxes( solvedVector, LimitRotation, out var axisX, out var axisY, out var axisZ );\n\t\taxisX = renderer.WorldTransform.Rotation * axisX;\n\t\taxisY = renderer.WorldTransform.Rotation * axisY;\n\t\taxisZ = renderer.WorldTransform.Rotation * axisZ;\n\t\tvar gizmoRotation = Rotation.FromAxis( axisY, 90f );\n\t\taxisX = gizmoRotation * axisX;\n\t\taxisZ = gizmoRotation * axisZ;\n\t\tGizmo.Draw.Color = LimitGizmoColor.WithAlpha( LimitGizmoOpacity );\n\t\tconst int steps = 24;\n\t\tif ( LimitType == SoftBoneLimitType.Cone )\n\t\t{\n\t\t\tDrawArc( worldBegin, axisY, axisX, maxAngle, radius, steps );\n\t\t\tDrawArc( worldBegin, axisY, axisZ, maxAngle, radius, steps );\n\t\t}\n\t\telse if ( LimitType == SoftBoneLimitType.Plane )\n\t\t{\n\t\t\tDrawArc( worldBegin, axisY, axisZ, maxAngle, radius, steps );\n\t\t\tGizmo.Draw.Line( worldBegin - axisX * radius * 0.45f, worldBegin \u002B axisX * radius * 0.45f );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDrawArc( worldBegin, axisY, axisX, maxYaw, radius, steps );\n\t\t\tDrawArc( worldBegin, axisY, axisZ, maxPitch, radius, steps );\n\t\t}\n\t}\n\n\tprivate static void DrawArc( Vector3 origin, Vector3 forward, Vector3 side, float angle, float radius, int steps )\n\t{\n\t\tvar previous = origin \u002B Rotation.FromAxis( side, -angle ) * forward * radius;\n\t\tfor ( var i = 1; i \u003C= steps; i\u002B\u002B )\n\t\t{\n\t\t\tvar point = origin \u002B Rotation.FromAxis( side, -angle \u002B angle * 2f * i / steps ) * forward * radius;\n\t\t\tGizmo.Draw.Line( previous, point );\n\t\t\tprevious = point;\n\t\t}\n\t}\n\n\tprivate SkinnedModelRenderer FindRendererForGizmo()\n\t{\n\t\tvar targetRenderer = TargetBone?.GetComponentInParent\u003CSkinnedModelRenderer\u003E( true, true );\n\t\tif ( targetRenderer is not null \u0026\u0026 targetRenderer.IsValid() )\n\t\t\treturn targetRenderer;\n\t\treturn null;\n\t}\n\n\tinternal SkinnedModelRenderer GetTargetRenderer() =\u003E FindRendererForGizmo();\n\n\tprotected override void OnValidate()\n\t{\n\t\tUpdateOwnerName();\n\t\tColliderGizmoOpacity = Math.Clamp( ColliderGizmoOpacity, 0f, 1f );\n\t\tLimitGizmoOpacity = Math.Clamp( LimitGizmoOpacity, 0f, 1f );\n\t\tColliderHeight = Math.Max( ColliderHeight, 0.002f );\n\t\tBoxDimension = new Vector3( Math.Max( BoxDimension.x, 0.001f ), Math.Max( BoxDimension.y, 0.001f ), Math.Max( BoxDimension.z, 0.001f ) );\n\t\tLimitGizmoSize = Math.Max( LimitGizmoSize, 0.1f );\n\t\tMarkDirty();\n\t}\n\n\tprotected override void OnDisabled()\n\t{\n\t\tSoftBoneCollisionRegistry.Remove( this );\n\t}\n}"},{"Ident":"bobicolab.bobicosoftbones","Path":"Code/BoBiCo Soft-Physics/SoftBoneSolver.cs","FileName":"SoftBoneSolver.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"using System;\n\n/// \u003Csummary\u003E\n/// Integrates one SoftBone link in renderer-local space.\n/// \u003C/summary\u003E\npublic static class BoBiCoSoftBoneSolver\n{\n\tprivate const float Epsilon = 0.000008f;\n\n\t/// \u003Csummary\u003E\n\t/// One integration step.  All positions/vectors in renderer-local space.\n\t/// Returns the solved end position (renderer-local) and the new velocity.\n\t/// \u003C/summary\u003E\n\tpublic static Vector3 IntegrateAdvanced(\n\t\tVector3 begin,\n\t\tVector3 endPoint,           // current physics end (may have had Immobile applied before call)\n\t\tVector3 previousEndPoint,   // endPoint before Immobile, for velocity storage\n\t\tVector3 poseVector,         // animated rest direction (renderer-local)\n\t\tVector3 previousVector,     // last frame\u0027s (solvedEnd - begin)\n\t\tVector3 previousVelocity,\n\t\tfloat minLength,\n\t\tfloat maxLength,\n\t\tfloat pull,\n\t\tfloat momentum,\n\t\tfloat stiffness,\n\t\tVector3 gravityTargetEnd,   // begin \u002B gravity-blended direction * restLength\n\t\tfloat frameRatio,           // Time.Delta * 60, used for frameLerp\n\t\tout Vector3 newVelocity )\n\t{\n\t\tvar delta = previousVelocity * momentum;\n\t\tdelta \u002B= (gravityTargetEnd - (endPoint \u002B delta)) * pull;\n\n\t\tif ( stiffness \u003E Epsilon )\n\t\t{\n\t\t\tvar nextVector = endPoint \u002B delta - begin;\n\t\t\tdelta \u002B= (previousVector - nextVector) * stiffness;\n\t\t}\n\n\t\tendPoint \u002B= delta;\n\t\tvar boundedVector = endPoint - begin;\n\t\tvar boundedLength = Math.Clamp( boundedVector.Length, minLength, maxLength );\n\t\tendPoint = begin \u002B SafeNormal( boundedVector, poseVector ) * boundedLength;\n\n\t\t// Limit per-step commitment so long frames do not inject a velocity spike.\n\t\tvar frameLerp = Math.Clamp( (1f / Lerp( 1f, frameRatio, pull )) * frameRatio, 0f, 1f );\n\t\tendPoint = Lerp( previousEndPoint, endPoint, frameLerp );\n\n\t\t// velocity = where we actually moved to, not the raw delta\n\t\tnewVelocity = endPoint - previousEndPoint;\n\n\t\treturn endPoint;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Solve the head bone rotation to point toward the simulated tail.\n\t/// Works in renderer-local space.\n\t/// \u003C/summary\u003E\n\tpublic static Rotation SolveHeadRotation(\n\t\tVector3 poseVector,         // animated rest direction (renderer-local)\n\t\tVector3 solvedVector,       // solvedEnd - begin (renderer-local)\n\t\tRotation animatedRotation )\n\t{\n\t\tvar restAxis = SafeNormal( poseVector, Vector3.Forward );\n\t\tvar solvedAxis = SafeNormal( solvedVector, restAxis );\n\t\treturn Rotation.FromToRotation( restAxis, solvedAxis ) * animatedRotation;\n\t}\n\n\t// \u2500\u2500 Helpers (all renderer-local) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\tpublic static Vector3 SafeNormal( Vector3 value, Vector3 fallback )\n\t{\n\t\tif ( value.Length \u003C= Epsilon )\n\t\t\treturn fallback.Length \u003E Epsilon ? fallback.Normal : Vector3.Forward;\n\n\t\treturn value.Normal;\n\t}\n\n\tprivate static Vector3 Lerp( Vector3 a, Vector3 b, float t ) =\u003E a \u002B (b - a) * t;\n\tprivate static float Lerp( float a, float b, float t ) =\u003E a \u002B (b - a) * t;\n}\n"},{"Ident":"bobicolab.bobicosoftbones","Path":".obj/__compiler_extra.cs","FileName":"__compiler_extra.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"global using static Sandbox.Internal.GlobalGameNamespace;\r\nglobal using Microsoft.AspNetCore.Components;\r\nglobal using Microsoft.AspNetCore.Components.Rendering;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonTitle\u0022, \u0022BoBiCo SoftBones\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonIdent\u0022, \u0022bobicosoftbones\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022OrgIdent\u0022, \u0022bobicolab\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022Ident\u0022, \u0022bobicolab.bobicosoftbones\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineVersion\u0022, \u002228\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineMinorVersion\u0022, \u00221\u0022 )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \u0022.NETCoreApp,Version=v9.0\u0022, FrameworkDisplayName = \u0022.NET 9.0\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022CompileTime\u0022, \u00222026-07-29T06:05:00.6415890Z\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\u00220.0.118.0\u0022)]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\u00220.0.118.0\u0022)]"},{"Ident":"bobicolab.bobicosoftbones","Path":"Code/BoBiCo Soft-Physics/Components/BoBiCoSoftBoneCollider.cs","FileName":"BoBiCoSoftBoneCollider.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"//=========================================================================================================================\n// BoBiCo Soft-Bone Collider Component\n//=========================================================================================================================\n\nusing System;\n\n[Title( \u0022SoftBone Collider\u0022 )]\n[Category( \u0022BoBiCo\u0022 )]\n[Icon( \u0022radio_button_unchecked\u0022 )]\n[HelpUrl( \u0022https://bobico-lab.vercel.app/softbone-docs/bobico-softbones/core-components/softbone-collider\u0022 )]\npublic sealed class BoBiCoSoftBoneCollider : Component, Component.ExecuteInEditor\n{\npublic enum ColliderShape\n{\n\t/// \u003Csummary\u003E\n\t/// A simple spherical collider.\n\t/// Best for rounded body parts and lightweight collision detection.\n\t/// \u003C/summary\u003E\n\tSphere,\n\n\t/// \u003Csummary\u003E\n\t/// A capsule-shaped collider.\n\t/// Recommended for most bones and elongated body parts.\n\t/// \u003C/summary\u003E\n\tCapsule,\n\n\t/// \u003Csummary\u003E\n\t/// A box-shaped collider.\n\t/// Useful for broad collision areas and block-like shapes.\n\t/// \u003C/summary\u003E\n\tBox,\n\n\t/// \u003Csummary\u003E\n\t/// A one-sided flat collider.\n\t/// Useful for flat surface areas.\n\t/// \u003C/summary\u003E\n\tFlat,\n}\n\n//=========================================================================================================================\n// Collider Properties\n//=========================================================================================================================\n\n\t/// \u003Csummary\u003E\n\t/// Offsets the position of the collider.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 1 ), Title( \u0022Position Offset\u0022 ), Group( \u0022Collider Properties\u0022 )]\n\tpublic Vector3 PositionOffset { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Offsets the rotation of the collider.\n\t/// \u003C/summary\u003E\n\t[Property, Order( 2 ), Title( \u0022Rotation Offset\u0022 ), Group( \u0022Collider Properties\u0022 )]\n\tpublic Angles RotationOffset { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// Target bone to which the collider is attached. \n\t/// \u003C/summary\u003E\n\t[Property, Order( 3 ), Title( \u0022Target Bone\u0022 ), Group( \u0022Collider Properties\u0022 )]\n\tpublic GameObject TargetBone { get; set; }\n\n//=========================================================================================================================\n// Shape Settings\n//=========================================================================================================================\n\n    /// \u003Csummary\u003E\n    /// Shape used for this chain\u0027s collision volume.\n    /// Different shapes provide different collision behavior and coverage.\n    /// \u003C/summary\u003E\n\t[Property, Order( 4 ), Title( \u0022Collider Shape\u0022 ), Group( \u0022Shape Settings\u0022 )]\n\tpublic ColliderShape Shape { get; set; } = ColliderShape.Sphere;\n\n    /// \u003Csummary\u003E\n    /// Radius of the chain\u0027s collision volume.\n    /// Larger values create thicker collision boundaries.\n    /// \u003C/summary\u003E\n\t[Property, Order( 5 ), Title( \u0022Collider Radius\u0022 ), Group( \u0022Shape Settings\u0022 ), HideIf( nameof( Shape ), ColliderShape.Box )]\n\tpublic float ColliderRadius { get; set; } = 2f;\n\n    /// \u003Csummary\u003E\n    /// Height of Capsule collider.\n    /// \u003C/summary\u003E\n\t[Property, Order( 6 ), Title( \u0022Collider Height\u0022 ), Group( \u0022Shape Settings\u0022 ), ShowIf( nameof( Shape ), ColliderShape.Capsule )]\n\tpublic float ColliderHeight { get; set; } = 10f;\n\n    /// \u003Csummary\u003E\n    /// Size of Box colliders.\n    /// Only applies when the collider shape is set to Box.\n    /// \u003C/summary\u003E\n\t[Property, Order( 7 ), Title( \u0022Box Dimension\u0022 ), Group( \u0022Shape Settings\u0022 ), ShowIf( nameof( Shape ), ColliderShape.Box )]\n\tpublic Vector3 BoxSize { get; set; } = new Vector3( 1.25f, 1.25f, 1.25f );\n\n//=========================================================================================================================\n// Collider Gizmo Settings\n//=========================================================================================================================\n\n    /// \u003Csummary\u003E\n    /// Displays the chain\u0027s collider in the Scene view.\n    /// Useful for visualizing collision shapes and sizes.\n    /// \u003C/summary\u003E\n\t[Property, Order( 8 ), Title( \u0022Collider Gizmo\u0022 ), Group( \u0022Gizmo Settings\u0022 )]\n\tpublic bool ShowColliderGizmo { get; set; } = true;\n\n    /// \u003Csummary\u003E\n    /// Keeps the collider gizmo visible even when the chain is not selected.\n    /// Useful for debugging collision setups.\n    /// \u003C/summary\u003E\n\t[Property, Order( 9 ), Title( \u0022Always Show Gizmo\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowColliderGizmo ), true )]\n\tpublic bool AlwaysShowGizmo { get; set; }\n\n    /// \u003Csummary\u003E\n    /// Color used for rendering the collider gizmo.\n    /// \u003C/summary\u003E\n\t[Property, Order( 10 ), Title( \u0022Collider Gizmo Color\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowColliderGizmo ), true )]\n\tpublic Color ColliderGizmoColor { get; set; } = Gizmo.Colors.Green;\n\n    /// \u003Csummary\u003E\n    /// Controls the transparency of the collider gizmo.\n    /// Lower values make the visualization less intrusive.\n    /// \u003C/summary\u003E\n\t[Property, Order( 11 ), Range( 0, 1 ), Title( \u0022Gizmo Opacity\u0022 ), Group( \u0022Gizmo Settings\u0022 ), ShowIf( nameof( ShowColliderGizmo ), true )]\n\tpublic float ColliderGizmoOpacity { get; set; } = 1f;\t\n\n//=========================================================================================================================\n// Collider Shape Calculation\n//=========================================================================================================================\n\tpublic bool TryGetWorldShape( SkinnedModelRenderer renderer, out SoftBoneColliderShape shape )\n\t{\n\t\tshape = default;\n\t\tvar boneName = TargetBone is not null \u0026\u0026 TargetBone.IsValid() ? TargetBone.Name?.Trim() : \u0022\u0022;\n\t\tif ( string.IsNullOrWhiteSpace( boneName ) )\n\t\t\treturn false;\n\n\t\tvar hasBoneTransform = renderer is not null \u0026\u0026 renderer.IsValid() \u0026\u0026 renderer.Model?.Bones is not null \u0026\u0026 renderer.Model.Bones.HasBone( boneName );\n\t\tif ( hasBoneTransform )\n\t\t{\n\t\t\tvar bone = renderer.Model.Bones.GetBone( boneName );\n\t\t\tif ( renderer.TryGetBoneTransformAnimation( bone, out var animatedTransform ) || renderer.TryGetBoneTransform( bone, out animatedTransform ) )\n\t\t\t\treturn BuildWorldShape( animatedTransform, out shape );\n\t\t}\n\n\t\treturn BuildWorldShape( TargetBone.WorldTransform, out shape );\n\t}\n\n\tprivate bool BuildWorldShape( Transform boneTransform, out SoftBoneColliderShape shape )\n\t{\n\t\tshape = default;\n\n\t\t// Swap pitch/yaw to match S\u0026box bone forward = X axis\n\t\tvar fixedRotOffset = new Angles( RotationOffset.roll, RotationOffset.pitch, RotationOffset.yaw );\n\t\tvar rotation = boneTransform.Rotation * fixedRotOffset.ToRotation();\n\t\tvar radius = Math.Max( ColliderRadius, 0.001f );\n\t\tvar height = Math.Max( ColliderHeight, radius * 2f );\n\t\t// Collider offsets use the existing VRC-style axis order: X=up, Y=forward, Z=right.\n\t\tvar remappedOffset = new Vector3( PositionOffset.y, PositionOffset.z, PositionOffset.x );\n\t\tvar center = boneTransform.PointToWorld( remappedOffset );\n\n\t\tif ( Shape == ColliderShape.Sphere || (Shape == ColliderShape.Capsule \u0026\u0026 height \u003C= radius * 2f) )\n\t\t{\n\t\t\tshape = SoftBoneColliderShape.Sphere( center, radius );\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( Shape == ColliderShape.Capsule )\n\t\t{\n\t\t\tvar halfLine = Math.Max( 0f, height * 0.5f - radius );\n\t\t\t// VRC capsules are authored along their local Up axis. Using Forward here\n\t\t\t// made S\u0026box Roll rotate around the capsule\u0027s own length, which is invisible.\n\t\t\tvar axis = rotation * Vector3.Forward;\n\t\t\tshape = SoftBoneColliderShape.Capsule( center - axis * halfLine, center \u002B axis * halfLine, radius );\n\t\t}\n\t\telse if ( Shape == ColliderShape.Box )\n\t\t\tshape = SoftBoneColliderShape.Box( center, rotation, new Vector3( Math.Max( BoxSize.z, 0.001f ), Math.Max( BoxSize.x, 0.001f ), Math.Max( BoxSize.y, 0.001f ) ) );\n\t\telse\n\t\t\tshape = SoftBoneColliderShape.Flat( center, rotation * Vector3.Up );\n\t\treturn true;\n\t}\n\n//=========================================================================================================================\n// Collider Gizmo Drawing\n//=========================================================================================================================\n\n\tprotected override void DrawGizmos()\n\t{\n\t\tbase.DrawGizmos();\n\t\tDrawColliderGizmo( AlwaysShowGizmo || Gizmo.IsSelected );\n\t}\n\n\tinternal void DrawColliderGizmo( bool visible, bool force = false )\n\t{\n\t\tif ( !visible || ( !force \u0026\u0026 !ShowColliderGizmo ) || !TryGetWorldShape( FindRenderer(), out var shape ) )\n\t\t\treturn;\n\n\t\t// Draw directly in world space so the sphere radius is never distorted by\n\t\t// the component\u0027s scale (which caused the squished ellipsoid).\n\t\tusing ( Gizmo.Scope( \u0022SoftBoneCollider\u0022 ) )\n\t\t{\n\t\t\tGizmo.Transform = new Transform( Vector3.Zero, Rotation.Identity, 1f );\n\t\t\tGizmo.Draw.IgnoreDepth = true;\n\t\t\tGizmo.Draw.LineThickness = 1;\n\t\t\tGizmo.Draw.Color = ColliderGizmoColor.WithAlpha( ColliderGizmoOpacity );\n\n\t\t\tif ( shape.IsSphere )\n\t\t\t\tGizmo.Draw.LineSphere( new Sphere( shape.Start, shape.Radius ) );\n\t\t\telse if ( shape.Type == SoftBoneColliderShapeType.Capsule )\n\t\t\t\tGizmo.Draw.LineCapsule( new Capsule( shape.Start, shape.End, shape.Radius ) );\n\t\t\telse if ( shape.Type == SoftBoneColliderShapeType.Box )\n\t\t\t{\n\t\t\t\tGizmo.Transform = new Transform( shape.Center, shape.Rotation, 1f );\n\t\t\t\tGizmo.Draw.LineBBox( BBox.FromPositionAndSize( Vector3.Zero, shape.Size ) );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tvar tangent = Vector3.Cross( shape.Normal, Math.Abs( Vector3.Dot( shape.Normal, Vector3.Up ) ) \u003C 0.99f ? Vector3.Up : Vector3.Right ).Normal;\n\t\t\t\tvar bitangent = Vector3.Cross( shape.Normal, tangent ).Normal;\n\t\t\t\tvar size = ColliderRadius;\n\t\t\t\tvar a = shape.Center \u002B (tangent \u002B bitangent) * size;\n\t\t\t\tvar b = shape.Center \u002B (tangent - bitangent) * size;\n\t\t\t\tvar c = shape.Center \u002B (-tangent - bitangent) * size;\n\t\t\t\tvar d = shape.Center \u002B (-tangent \u002B bitangent) * size;\n\t\t\t\tGizmo.Draw.Line( a, b ); Gizmo.Draw.Line( b, c ); Gizmo.Draw.Line( c, d ); Gizmo.Draw.Line( d, a );\n\t\t\t\tGizmo.Draw.Line( shape.Center, shape.Center \u002B shape.Normal * 8f );\n\t\t\t}\n\t\t}\n\t}\n\n//=========================================================================================================================\n// Collider Bone Lookup\n//=========================================================================================================================\n\n\tprivate SkinnedModelRenderer FindRenderer()\n\t{\n\t\tvar targetRenderer = TargetBone?.GetComponentInParent\u003CSkinnedModelRenderer\u003E( true, true );\n\t\tif ( targetRenderer is not null \u0026\u0026 targetRenderer.IsValid() )\n\t\t\treturn targetRenderer;\n\t\treturn null;\n\t}\n\n\tinternal SkinnedModelRenderer GetOwningRenderer() =\u003E FindRenderer();\n\n\tprotected override void OnValidate()\n\t{\n\t\tColliderRadius = Math.Max( ColliderRadius, 0.001f );\n\t\tColliderHeight = Math.Max( ColliderHeight, ColliderRadius * 2f );\n\t\tBoxSize = new Vector3( Math.Max( BoxSize.x, 0.001f ), Math.Max( BoxSize.y, 0.001f ), Math.Max( BoxSize.z, 0.001f ) );\n\t\tColliderGizmoOpacity = Math.Clamp( ColliderGizmoOpacity, 0f, 1f );\n\t}\n}"},{"Ident":"bobicolab.bobicosoftbones","Path":"Code/BoBiCo Soft-Physics/Components/BoBiCoSoftBoneColliderGroup.cs","FileName":"BoBiCoSoftBoneColliderGroup.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":336846,"IsPrivate":false,"Code":"//=========================================================================================================================\n// BoBiCo Soft-Bone Collider Group Component\n//=========================================================================================================================\n\nusing System.Collections.Generic;\n\n[Title( \u0022SoftBone Collider Group\u0022 )]\n[Category( \u0022BoBiCo\u0022 )]\n[Icon( \u0022category\u0022 )]\n[HelpUrl( \u0022https://bobico-lab.vercel.app/softbone-docs/bobico-softbones/core-components/softbone-collider-group\u0022 )]\npublic sealed class BoBiCoSoftBoneColliderGroup : Component, Component.ExecuteInEditor\n{\n\n//=========================================================================================================================\n// Group Setup\n//=========================================================================================================================\n\n\t/// \u003Csummary\u003E\n\t/// List of colliders that are part of this group. These colliders will be used for collision detection with soft-bone chains.\n\t/// \u003C/summary\u003E\n\t[Property, Title( \u0022Colliders\u0022 ), Group( \u0022Colliders\u0022 )]\n\tpublic List\u003CGameObject\u003E Colliders { get; set; } = new();\n\n    // Collect all valid colliders from the group and add them to the output list, avoiding duplicates using the seen set.\n\tpublic void CollectColliders( List\u003CBoBiCoSoftBoneCollider\u003E output, HashSet\u003CBoBiCoSoftBoneCollider\u003E seen )\n\t{\n\t\tif ( !Enabled || Colliders is null )\n\t\t\treturn;\n\n\t\tforeach ( var colliderObject in Colliders )\n\t\t{\n\t\t\tif ( colliderObject is null || !colliderObject.IsValid() )\n\t\t\t\tcontinue;\n\n\t\t\tvar collider = colliderObject.GetComponent\u003CBoBiCoSoftBoneCollider\u003E();\n\t\t\tif ( collider is not null \u0026\u0026 collider.Enabled \u0026\u0026 seen.Add( collider ) )\n\t\t\t\toutput.Add( collider );\n\t\t}\n\t}\n}\n"}]}