// using System; // using NBF; // using UnityEngine; // // [RequireComponent(typeof(LineRenderer))] // public class Rope : MonoBehaviour // { // [Header("Anchors")] [SerializeField] public Rigidbody startAnchor; // [SerializeField] public Rigidbody endAnchor; // // /// 鱼线宽度倍数 // public int LineMultiple = 1; // // [Header("Physics (Dynamic Nodes, Fixed Segment Len)")] [SerializeField, Min(0.01f), Tooltip("物理每段固定长度(越小越细致越耗)")] // private float physicsSegmentLen = 0.15f; // // [SerializeField, Range(2, 200)] private int minPhysicsNodes = 12; // // [SerializeField, Range(2, 400), Tooltip("物理节点上限(仅用于性能保护;与“最大长度不限制”不是一回事)")] // private int maxPhysicsNodes = 120; // // [SerializeField] private float gravityStrength = 2.0f; // [SerializeField, Range(0f, 1f)] private float velocityDampen = 0.95f; // // [SerializeField, Range(0.0f, 1.0f), Tooltip("约束修正强度,越大越硬。0.6~0.9 常用")] // private float stiffness = 0.8f; // // [SerializeField, Range(1, 80), Tooltip("迭代次数。鱼线 10~30 通常够用")] // private int iterations = 20; // // [SerializeField, Range(0, 16), Tooltip("主求解后追加的硬长度约束次数。只负责把 poly 拉回到 rest total,不改变可变长度逻辑")] // private int hardTightenIterations = 2; // // [Header("Length Control (No Min/Max Clamp)")] // [Tooltip("初始总长度(米)。如果为 0,则用 physicsSegmentLen*(minPhysicsNodes-1) 作为初始长度")] // [SerializeField, Min(0f)] // private float initialLength = 0f; // // [Tooltip("长度变化平滑时间(越小越跟手,越大越稳)")] [SerializeField, Min(0.0001f)] // private float lengthSmoothTime = 0.15f; // // [Tooltip("当长度在变化时,额外把速度压掉一些(防抖)。0=不额外处理,1=变化时几乎清速度(建议只在收线生效)")] [SerializeField, Range(0f, 1f)] // private float lengthChangeVelocityKill = 0.6f; // // [Tooltip("允许的最小松弛余量(避免目标长度刚好等于锚点距离时抖动)")] [SerializeField, Min(0f)] // private float minSlack = 0.002f; // // [Header("Head Segment Clamp")] [Tooltip("第一段(起点->第1节点)允许的最小长度,避免收线时第一段被压到0导致数值炸")] [SerializeField, Min(0.0001f)] // private float headMinLen = 0.01f; // // [Header("Node Count Stability")] [SerializeField, Tooltip("节点数切换迟滞(米)。避免长度在临界点抖动导致节点数来回跳 -> 卡顿")] // private float nodeHysteresis = 0.05f; // // [Header("Simple Ground/Water Constraint (Cheap)")] [SerializeField] // private bool constrainToGround = true; // // [SerializeField] private LayerMask groundMask = ~0; // [SerializeField, Min(0f)] private float groundRadius = 0.01f; // [SerializeField, Min(0f)] private float groundCastHeight = 1.0f; // [SerializeField, Min(0.01f)] private float groundCastDistance = 2.5f; // // [SerializeField, Range(1, 8), Tooltip("每隔多少个节点做一次地面检测;越大越省")] // private int groundSampleStep = 3; // // [SerializeField, Tooltip("未采样的点用插值还是直接拷贝邻近采样值")] // private bool groundInterpolate = true; // // [SerializeField, Range(1, 8), Tooltip("每隔多少次FixedUpdate更新一次地面约束")] // private int groundUpdateEvery = 2; // // [SerializeField, Range(0, 8), Tooltip("地面约束后,再做几次长度约束,减少 poly 被地面抬长")] // private int groundPostConstraintIterations = 2; // // private int _groundFrameCounter; // // [Header("Simple Water Float (Cheap)")] [SerializeField, Tooltip("绳子落到水面以下时,是否把节点约束回水面")] // private bool constrainToWaterSurface = true; // // [SerializeField, Tooltip("静态水面高度;如果你后面接波浪水面,可改成采样函数")] // private float waterLevelY = 0f; // // [SerializeField, Min(0f), Tooltip("把线抬到水面上方一点,避免视觉穿插")] // private float waterSurfaceOffset = 0.002f; // // [SerializeField, Range(1, 8), Tooltip("每隔多少个节点做一次水面约束采样;越大越省")] // private int waterSampleStep = 2; // // [SerializeField, Tooltip("未采样节点是否插值水面高度")] // private bool waterInterpolate = true; // // [SerializeField, Range(1, 8), Tooltip("每隔多少次FixedUpdate更新一次水面约束")] // private int waterUpdateEvery = 1; // // [SerializeField, Range(0f, 1f), Tooltip("水面约束抬升强度(每次更新的插值强度),越小越渐进")] // private float waterLiftStrength = 0.25f; // // [SerializeField, Tooltip("startAnchor 在水下时,让其相邻端节点强制跟随 startAnchor,避免被抬到水面导致脱离")] // private bool keepStartAdjacentNodeFollow = true; // // [SerializeField, Range(0, 8), Tooltip("水面约束后,再做几次长度约束,减少局部折角")] // private int waterPostConstraintIterations = 2; // // private int _waterFrameCounter; // // [Header("Render (High Resolution)")] [SerializeField, Min(1), Tooltip("静止时每段物理线段插值加密数量(越大越顺,越耗)")] // private int renderSubdivisionsIdle = 6; // // [SerializeField, Min(1), Tooltip("甩动时每段物理线段插值加密数量(动态降LOD以防卡顿)")] // private int renderSubdivisionsMoving = 2; // // [SerializeField, Min(0f), Tooltip("平均速度超过该阈值认为在甩动(用于动态降 subdiv)")] // private float movingSpeedThreshold = 2.0f; // // [SerializeField, Tooltip("是否使用 Catmull-Rom 平滑(开启更顺,但更耗)")] // private bool smooth = true; // // [SerializeField, Min(0.0001f)] private float lineWidth = 0.001f; // // [Header("Performance")] [SerializeField, Tooltip("远端玩家鱼线不可见时,直接停止整条渲染线的模拟与绘制")] // private bool cullRemoteRopeWhenInvisible = true; // // [SerializeField, Tooltip("本地玩家自己的鱼线始终保持完整计算")] // private bool localOwnerAlwaysSimulate = true; // // [SerializeField, Range(1, 60), Tooltip("每隔多少个 FixedUpdate 重新判断一次可见性")] // private int visibilityCheckEvery = 10; // // [SerializeField, Range(0f, 0.5f), Tooltip("屏幕边缘额外留白,避免刚进视野就闪现")] // private float visibilityViewportPadding = 0.08f; // // [Header("Air Drag (Stable)")] [SerializeField, Range(0f, 5f), Tooltip("空气阻力(Y向),指数衰减,越大越不飘")] // private float airDrag = 0.9f; // // [SerializeField, Range(0f, 2f), Tooltip("横向额外阻力(XZ),指数衰减,越大越不左右飘")] // private float airDragXZ = 0.6f; // // private LineRenderer _lineRenderer; // // // physics // private int _physicsNodes; // private Vector3[] _pCurr; // private Vector3[] _pPrev; // // // render (一次性分配到最大,后续不再 new) // private Vector3[] _rPoints; // private int _rCapacity; // // private Vector3 _gravity; // // // length control runtime // private float _targetLength; // private float _currentLength; // private float _lengthSmoothVel; // // // rest length head // private float _headRestLen; // // // node stability // private int _lastDesiredNodes = 0; // // // caches // private Transform _startTr; // private Transform _endTr; // // // precomputed // private float _dt; // private float _dt2; // private float _kY; // private float _kXZ; // private Transform _cameraTr; // private int _visibilityCheckCounter; // private bool _isCulledByVisibility; // private int _tIdleSubdiv = -1; // private int _tMovingSubdiv = -1; // // private FRod _rod; // public void Init(FRod rod) // { // _rod = rod; // if (Application.isPlaying) // RefreshVisibilityState(true); // } // // // Catmull t caches(只缓存 idle/moving 两档,减少每帧重复乘法) // private struct TCaches // { // public float[] t; // public float[] t2; // public float[] t3; // } // // private TCaches _tIdle; // private TCaches _tMoving; // // private void Awake() // { // _lineRenderer = GetComponent(); // _gravity = new Vector3(0f, -gravityStrength, 0f); // // RefreshAnchorTransforms(); // // InitLengthSystem(); // AllocateAndInitNodes(); // EnsureRenderCaches(); // RefreshVisibilityState(true); // } // // private void OnValidate() // { // renderSubdivisionsIdle = Mathf.Max(renderSubdivisionsIdle, 1); // renderSubdivisionsMoving = Mathf.Max(renderSubdivisionsMoving, 1); // iterations = Mathf.Clamp(iterations, 1, 80); // hardTightenIterations = Mathf.Clamp(hardTightenIterations, 0, 16); // groundCastDistance = Mathf.Max(groundCastDistance, 0.01f); // groundCastHeight = Mathf.Max(groundCastHeight, 0f); // lineWidth = Mathf.Max(lineWidth, 0.0001f); // // lengthSmoothTime = Mathf.Max(lengthSmoothTime, 0.0001f); // // physicsSegmentLen = Mathf.Max(physicsSegmentLen, 0.01f); // minPhysicsNodes = Mathf.Max(minPhysicsNodes, 2); // maxPhysicsNodes = Mathf.Max(maxPhysicsNodes, minPhysicsNodes); // // headMinLen = Mathf.Max(headMinLen, 0.0001f); // nodeHysteresis = Mathf.Max(0f, nodeHysteresis); // // groundSampleStep = Mathf.Max(1, groundSampleStep); // groundUpdateEvery = Mathf.Max(1, groundUpdateEvery); // groundPostConstraintIterations = Mathf.Clamp(groundPostConstraintIterations, 0, 8); // // waterSampleStep = Mathf.Max(1, waterSampleStep); // waterUpdateEvery = Mathf.Max(1, waterUpdateEvery); // waterSurfaceOffset = Mathf.Max(0f, waterSurfaceOffset); // waterLiftStrength = Mathf.Clamp01(waterLiftStrength); // waterPostConstraintIterations = Mathf.Clamp(waterPostConstraintIterations, 0, 8); // visibilityCheckEvery = Mathf.Clamp(visibilityCheckEvery, 1, 60); // visibilityViewportPadding = Mathf.Clamp(visibilityViewportPadding, 0f, 0.5f); // } // // private void RefreshAnchorTransforms() // { // _startTr = startAnchor ? startAnchor.transform : null; // _endTr = endAnchor ? endAnchor.transform : null; // } // // private bool ShouldAlwaysSimulate() // { // if (!localOwnerAlwaysSimulate) // return false; // // var owner = _rod?.PlayerItem?.Owner; // return owner == null || owner.IsSelf; // } // // private Transform GetActiveCameraTransform() // { // Camera main = BaseCamera.Main; // if (main) // { // _cameraTr = main.transform; // return _cameraTr; // } // // if (!_cameraTr) // { // Camera fallback = Camera.main; // if (fallback) // _cameraTr = fallback.transform; // } // // return _cameraTr; // } // // private static bool IsViewportPointVisible(Vector3 viewportPoint, float padding) // { // if (viewportPoint.z <= 0f) // return false; // // return viewportPoint.x >= -padding && viewportPoint.x <= 1f + padding && // viewportPoint.y >= -padding && viewportPoint.y <= 1f + padding; // } // // private bool IsVisibleToMainCamera() // { // Transform camTr = GetActiveCameraTransform(); // if (!camTr) // return true; // // Camera cam = camTr.GetComponent(); // if (!cam) // cam = BaseCamera.Main ? BaseCamera.Main : Camera.main; // if (!cam) // return true; // // Vector3 start = _startTr ? _startTr.position : (startAnchor ? startAnchor.position : transform.position); // Vector3 end = _endTr ? _endTr.position : (endAnchor ? endAnchor.position : transform.position); // Vector3 middle = (start + end) * 0.5f; // float padding = visibilityViewportPadding; // // return IsViewportPointVisible(cam.WorldToViewportPoint(start), padding) || // IsViewportPointVisible(cam.WorldToViewportPoint(end), padding) || // IsViewportPointVisible(cam.WorldToViewportPoint(middle), padding); // } // // private void RefreshVisibilityState(bool force = false) // { // if (!cullRemoteRopeWhenInvisible || ShouldAlwaysSimulate()) // { // _isCulledByVisibility = false; // if (_lineRenderer) // _lineRenderer.enabled = true; // return; // } // // if (!force) // { // _visibilityCheckCounter++; // if (_visibilityCheckCounter < visibilityCheckEvery) // return; // } // // _visibilityCheckCounter = 0; // bool wasCulled = _isCulledByVisibility; // _isCulledByVisibility = !IsVisibleToMainCamera(); // // if (_lineRenderer) // _lineRenderer.enabled = !_isCulledByVisibility; // // if (wasCulled && !_isCulledByVisibility) // SyncVisibleStateAfterCulling(); // } // // private void SyncVisibleStateAfterCulling() // { // _currentLength = Mathf.Max(_targetLength, 0.01f); // UpdateNodesFromLength(); // UpdateHeadRestLenFromCurrentLength(); // ResetNodesBetweenAnchors(); // LockAnchorsHard(); // } // // private void ResetNodesBetweenAnchors() // { // if (_physicsNodes < 2) // return; // // Vector3 start = _startTr ? _startTr.position : (startAnchor ? startAnchor.position : transform.position); // Vector3 end = _endTr ? _endTr.position : (endAnchor ? endAnchor.position : transform.position); // int last = _physicsNodes - 1; // // for (int i = 0; i <= last; i++) // { // float t = (last > 0) ? i / (float)last : 0f; // Vector3 pos = Vector3.Lerp(start, end, t); // _pCurr[i] = pos; // _pPrev[i] = pos; // } // } // // private void EnsureRenderCaches() // { // int idle = Mathf.Max(1, renderSubdivisionsIdle); // if (_tIdleSubdiv != idle) // { // BuildTCaches(idle, ref _tIdle); // _tIdleSubdiv = idle; // } // // int moving = Mathf.Max(1, renderSubdivisionsMoving); // if (_tMovingSubdiv != moving) // { // BuildTCaches(moving, ref _tMoving); // _tMovingSubdiv = moving; // } // // int maxSubdiv = Mathf.Max(idle, moving); // int neededCapacity = (maxPhysicsNodes - 1) * maxSubdiv + 1; // if (_rPoints == null || neededCapacity > _rCapacity) // { // _rCapacity = neededCapacity; // _rPoints = new Vector3[_rCapacity]; // } // } // // private void InitLengthSystem() // { // float defaultLen = physicsSegmentLen * (Mathf.Max(minPhysicsNodes, 2) - 1); // _currentLength = (initialLength > 0f) ? initialLength : defaultLen; // _targetLength = _currentLength; // } // // private void AllocateAndInitNodes() // { // _physicsNodes = Mathf.Clamp(ComputeDesiredNodesStable(_currentLength), 2, maxPhysicsNodes); // // _pCurr = new Vector3[maxPhysicsNodes]; // _pPrev = new Vector3[maxPhysicsNodes]; // // Vector3 start = startAnchor ? startAnchor.position : transform.position; // Vector3 dir = Vector3.down; // // for (int i = 0; i < _physicsNodes; i++) // { // Vector3 pos = start + dir * (physicsSegmentLen * i); // _pCurr[i] = pos; // _pPrev[i] = pos; // } // // UpdateHeadRestLenFromCurrentLength(); // // if (startAnchor && endAnchor) // LockAnchorsHard(); // } // // private int ComputeDesiredNodes(float lengthMeters) // { // int desired = Mathf.RoundToInt(Mathf.Max(0f, lengthMeters) / physicsSegmentLen) + 1; // desired = Mathf.Clamp(desired, minPhysicsNodes, maxPhysicsNodes); // return desired; // } // // private int ComputeDesiredNodesStable(float lengthMeters) // { // int desired = ComputeDesiredNodes(lengthMeters); // // if (_lastDesiredNodes == 0) // { // _lastDesiredNodes = desired; // return desired; // } // // if (desired == _lastDesiredNodes) // return desired; // // float boundary = (_lastDesiredNodes - 1) * physicsSegmentLen; // if (Mathf.Abs(lengthMeters - boundary) < nodeHysteresis) // return _lastDesiredNodes; // // _lastDesiredNodes = desired; // return desired; // } // // public void SetTargetLength(float lengthMeters) => _targetLength = Mathf.Max(0f, lengthMeters); // public float GetCurrentLength() => _currentLength; // public float GetTargetLength() => _targetLength; // public float GetLengthSmoothVel() => _lengthSmoothVel; // // public float GetLengthByPoints() // { // if (!smooth) // return GetPhysicsPolylineLength(); // // if (_rPoints == null || _lineRenderer == null) return 0f; // // int count = _lineRenderer.positionCount; // if (count < 2) return 0f; // // float totalLength = 0f; // for (int i = 1; i < count; i++) // { // Vector3 a = _rPoints[i - 1]; // Vector3 b = _rPoints[i]; // totalLength += Vector3.Distance(a, b); // } // // return totalLength; // } // // public float GetPhysicsPolylineLength() // { // float total = 0f; // for (int i = 1; i < _physicsNodes; i++) // total += Vector3.Distance(_pCurr[i - 1], _pCurr[i]); // return total; // } // // public void DebugLength() // { // float solverRestTotal = (_physicsNodes - 2) * physicsSegmentLen + _headRestLen; // float poly = GetPhysicsPolylineLength(); // float maxSegDelta = 0f; // float avgSegDelta = 0f; // for (int i = 1; i < _physicsNodes; i++) // { // float rest = (i == 1) ? _headRestLen : physicsSegmentLen; // float segLen = Vector3.Distance(_pCurr[i - 1], _pCurr[i]); // float delta = segLen - rest; // if (delta > maxSegDelta) maxSegDelta = delta; // avgSegDelta += delta; // } // // if (_physicsNodes > 1) // avgSegDelta /= (_physicsNodes - 1); // // Debug.Log( // $"current={_currentLength}, target={_targetLength}, nodes={_physicsNodes}, " + // $"seg={physicsSegmentLen}, head={_headRestLen}, headMin={headMinLen}, " + // $"solverRestTotal={solverRestTotal}, poly={poly}, delta={poly - solverRestTotal}, " + // $"maxSegDelta={maxSegDelta}, avgSegDelta={avgSegDelta}" // ); // } // // private void FixedUpdate() // { // if (!startAnchor || !endAnchor) return; // // RefreshAnchorTransforms(); // RefreshVisibilityState(); // if (_isCulledByVisibility) // return; // // _dt = Time.fixedDeltaTime; // if (_dt < 1e-6f) _dt = 1e-6f; // _dt2 = _dt * _dt; // // _gravity.y = -gravityStrength; // // _kY = Mathf.Exp(-airDrag * _dt); // _kXZ = Mathf.Exp(-airDragXZ * _dt); // // UpdateLengthSmooth(); // UpdateNodesFromLength(); // UpdateHeadRestLenFromCurrentLength(); // // Simulate_VerletFast(); // // // for (int it = 0; it < iterations; it++) // { // LockAnchorsHard(); // SolveDistanceConstraints_HeadOnly_Fast(); // } // // SolveHardDistanceConstraints(hardTightenIterations); // LockAnchorsHard(); // // if (constrainToGround) // { // _groundFrameCounter++; // if (_groundFrameCounter >= groundUpdateEvery) // { // _groundFrameCounter = 0; // ConstrainToGround(); // SolveHardDistanceConstraints(groundPostConstraintIterations); // } // } // // if (constrainToWaterSurface) // { // _waterFrameCounter++; // if (_waterFrameCounter >= waterUpdateEvery) // { // _waterFrameCounter = 0; // ConstrainToWaterSurface(); // // // 水面抬升后补几次长度约束,让形状更顺一点 // SolveHardDistanceConstraints(waterPostConstraintIterations); // } // } // // LockAnchorsHard(); // } // // private void Update() // { // if (!startAnchor || !endAnchor || _pCurr == null || _physicsNodes < 2) return; // // RefreshAnchorTransforms(); // if (_isCulledByVisibility) // return; // // EnsureRenderCaches(); // // int last = _physicsNodes - 1; // // Vector3 s = _startTr.position; // Vector3 e = _endTr.position; // // _pCurr[0] = s; // _pCurr[last] = e; // // _pPrev[0] = s; // // _pPrev[last] = e; // // DrawHighResLine_Fast(); // } // // private void UpdateLengthSmooth() // { // float minFeasible = 0.01f; // float desired = Mathf.Max(_targetLength, minFeasible); // // _currentLength = Mathf.SmoothDamp( // _currentLength, // desired, // ref _lengthSmoothVel, // lengthSmoothTime, // Mathf.Infinity, // Time.fixedDeltaTime // ); // // // 长度变化时额外压一点速度,减少收放线时抖动 // float delta = Mathf.Abs(_targetLength - _currentLength); // if (delta > 0.0001f && lengthChangeVelocityKill > 0f) // { // float keep = 1f - Mathf.Clamp01(lengthChangeVelocityKill); // for (int i = 1; i < _physicsNodes - 1; i++) // { // Vector3 curr = _pCurr[i]; // Vector3 prev = _pPrev[i]; // Vector3 disp = curr - prev; // _pPrev[i] = curr - disp * keep; // } // } // } // // private void UpdateNodesFromLength() // { // int desired = ComputeDesiredNodesStable(_currentLength); // desired = Mathf.Clamp(desired, 2, maxPhysicsNodes); // if (desired == _physicsNodes) return; // // if (desired > _physicsNodes) AddNodesAtStart(desired - _physicsNodes); // else RemoveNodesAtStart(_physicsNodes - desired); // // _physicsNodes = desired; // } // // private void AddNodesAtStart(int addCount) // { // if (addCount <= 0) return; // // int oldCount = _physicsNodes; // int newCount = Mathf.Min(oldCount + addCount, maxPhysicsNodes); // addCount = newCount - oldCount; // if (addCount <= 0) return; // // Array.Copy(_pCurr, 1, _pCurr, 1 + addCount, oldCount - 1); // Array.Copy(_pPrev, 1, _pPrev, 1 + addCount, oldCount - 1); // // Vector3 s = _startTr ? _startTr.position : startAnchor.position; // // Vector3 dir = Vector3.down; // int firstOld = 1 + addCount; // if (oldCount >= 2 && firstOld < maxPhysicsNodes) // { // Vector3 toOld1 = (_pCurr[firstOld] - s); // float sq = toOld1.sqrMagnitude; // if (sq > 1e-6f) dir = toOld1 / Mathf.Sqrt(sq); // } // // Vector3 inheritDisp = Vector3.zero; // if (oldCount >= 2 && firstOld < maxPhysicsNodes) // inheritDisp = (_pCurr[firstOld] - _pPrev[firstOld]); // // for (int k = 1; k <= addCount; k++) // { // Vector3 pos = s + dir * (physicsSegmentLen * k); // _pCurr[k] = pos; // _pPrev[k] = pos - inheritDisp; // } // // LockAnchorsHard(); // } // // private void RemoveNodesAtStart(int removeCount) // { // if (removeCount <= 0) return; // // int oldCount = _physicsNodes; // int newCount = Mathf.Max(oldCount - removeCount, 2); // removeCount = oldCount - newCount; // if (removeCount <= 0) return; // // Array.Copy(_pCurr, 1 + removeCount, _pCurr, 1, newCount - 2); // Array.Copy(_pPrev, 1 + removeCount, _pPrev, 1, newCount - 2); // // LockAnchorsHard(); // } // // private void UpdateHeadRestLenFromCurrentLength() // { // int fixedSegCount = Mathf.Max(0, _physicsNodes - 2); // float baseLen = fixedSegCount * physicsSegmentLen; // // _headRestLen = _currentLength - baseLen; // _headRestLen = Mathf.Clamp(_headRestLen, headMinLen, physicsSegmentLen * 1.5f); // } // // private void Simulate_VerletFast() // { // for (int i = 1; i < _physicsNodes - 1; i++) // { // Vector3 disp = _pCurr[i] - _pPrev[i]; // // disp.x *= _kXZ; // disp.z *= _kXZ; // disp.y *= _kY; // // disp *= velocityDampen; // // Vector3 next = _pCurr[i] + disp + _gravity * _dt2; // // _pPrev[i] = _pCurr[i]; // _pCurr[i] = next; // } // } // // private void LockAnchorsHard() // { // if (!startAnchor || !endAnchor || _pCurr == null || _pPrev == null || _physicsNodes < 2) return; // // Vector3 s = _startTr ? _startTr.position : startAnchor.position; // Vector3 e = _endTr ? _endTr.position : endAnchor.position; // // _pCurr[0] = s; // _pPrev[0] = s - startAnchor.linearVelocity * _dt; // // int last = _physicsNodes - 1; // _pCurr[last] = e; // _pPrev[last] = e - endAnchor.linearVelocity * _dt; // } // // private void SolveDistanceConstraints_HeadOnly_Fast() // { // SolveDistanceConstraints_HeadOnly_Bidirectional(stiffness); // } // // private void SolveHardDistanceConstraints(int extraIterations) // { // for (int it = 0; it < extraIterations; it++) // { // LockAnchorsHard(); // SolveDistanceConstraints_HeadOnly_Hard(); // } // } // // private void SolveDistanceConstraints_HeadOnly_Hard() // { // SolveDistanceConstraints_HeadOnly_Bidirectional(1f); // } // // private void SolveDistanceConstraints_HeadOnly_Bidirectional(float combinedStiffness) // { // int last = _physicsNodes - 1; // if (last <= 0) return; // // float clamped = Mathf.Clamp01(combinedStiffness); // float sweepStiffness = (clamped >= 0.999999f) ? 1f : 1f - Mathf.Sqrt(1f - clamped); // SolveDistanceConstraintsSweep_Fast(0, last, 1, last, sweepStiffness); // SolveDistanceConstraintsSweep_Fast(last - 1, -1, -1, last, sweepStiffness); // } // // private void SolveDistanceConstraintsSweep_Fast(int start, int endExclusive, int step, int last, float sweepStiffness) // { // for (int i = start; i != endExclusive; i += step) // { // float rest = (i == 0) ? _headRestLen : physicsSegmentLen; // // Vector3 a = _pCurr[i]; // Vector3 b = _pCurr[i + 1]; // // Vector3 delta = b - a; // float sq = delta.sqrMagnitude; // if (sq < 1e-12f) continue; // // float dist = Mathf.Sqrt(sq); // float diff = (dist - rest) / dist; // Vector3 corr = delta * (diff * sweepStiffness); // // bool aLocked = (i == 0); // bool bLocked = (i + 1 == last); // // if (!aLocked && !bLocked) // { // _pCurr[i] = a + corr * 0.5f; // _pCurr[i + 1] = b - corr * 0.5f; // } // else if (aLocked && !bLocked) // { // _pCurr[i + 1] = b - corr; // 首段:node1 吃满 // } // else if (!aLocked) // { // _pCurr[i] = a + corr; // 尾段:last-1 吃满 // } // // 两边都锁的情况理论上不会出现 // } // } // // private void ConstrainToGround() // { // if (groundMask == 0) return; // // int last = _physicsNodes - 1; // int step = Mathf.Max(1, groundSampleStep); // // int prevSampleIdx = 1; // float prevMinY = SampleMinY(_pCurr[prevSampleIdx]); // // ApplyMinY(prevSampleIdx, prevMinY); // // for (int i = 1 + step; i < last; i += step) // { // float nextMinY = SampleMinY(_pCurr[i]); // ApplyMinY(i, nextMinY); // // if (groundInterpolate) // { // int a = prevSampleIdx; // int b = i; // int span = b - a; // for (int j = 1; j < span; j++) // { // int idx = a + j; // float t = j / (float)span; // float minY = Mathf.Lerp(prevMinY, nextMinY, t); // ApplyMinY(idx, minY); // } // } // else // { // for (int idx = prevSampleIdx + 1; idx < i; idx++) // ApplyMinY(idx, prevMinY); // } // // prevSampleIdx = i; // prevMinY = nextMinY; // } // // for (int i = prevSampleIdx + 1; i < last; i++) // ApplyMinY(i, prevMinY); // } // // private float SampleMinY(Vector3 p) // { // Vector3 origin = p + Vector3.up * groundCastHeight; // if (Physics.Raycast(origin, Vector3.down, out RaycastHit hit, groundCastDistance, groundMask, // QueryTriggerInteraction.Ignore)) // return hit.point.y + groundRadius; // // return float.NegativeInfinity; // } // // private void ApplyMinY(int i, float minY) // { // if (float.IsNegativeInfinity(minY)) return; // // Vector3 p = _pCurr[i]; // if (p.y < minY) // { // p.y = minY; // _pCurr[i] = p; // // // prev 同步抬上来,避免下一帧又被惯性拉回去造成抖动 // Vector3 prev = _pPrev[i]; // if (prev.y < minY) prev.y = minY; // _pPrev[i] = prev; // } // } // // private void ConstrainToWaterSurface() // { // int last = _physicsNodes - 1; // if (last <= 1) return; // // int step = Mathf.Max(1, waterSampleStep); // float surfaceY = waterLevelY + waterSurfaceOffset; // bool startUnderWater = _pCurr[0].y < surfaceY; // int startAdjacentIdx = GetStartAdjacentNodeIndex(last); // // int prevSampleIdx = 1; // float prevSurfaceY = surfaceY; // // ApplyWaterSurface(prevSampleIdx, prevSurfaceY, startUnderWater, startAdjacentIdx); // // for (int i = 1 + step; i < last; i += step) // { // float nextSurfaceY = surfaceY; // ApplyWaterSurface(i, nextSurfaceY, startUnderWater, startAdjacentIdx); // // if (waterInterpolate) // { // int a = prevSampleIdx; // int b = i; // int span = b - a; // for (int j = 1; j < span; j++) // { // int idx = a + j; // float t = j / (float)span; // float y = Mathf.Lerp(prevSurfaceY, nextSurfaceY, t); // ApplyWaterSurface(idx, y, startUnderWater, startAdjacentIdx); // } // } // else // { // for (int idx = prevSampleIdx + 1; idx < i; idx++) // ApplyWaterSurface(idx, prevSurfaceY, startUnderWater, startAdjacentIdx); // } // // prevSampleIdx = i; // prevSurfaceY = nextSurfaceY; // } // // for (int i = prevSampleIdx + 1; i < last; i++) // ApplyWaterSurface(i, prevSurfaceY, startUnderWater, startAdjacentIdx); // } // // private int GetStartAdjacentNodeIndex(int last) // { // if (last <= 1) return 1; // // Vector3 s = _pCurr[0]; // float d1 = (_pCurr[1] - s).sqrMagnitude; // float d2 = (_pCurr[last - 1] - s).sqrMagnitude; // return d1 <= d2 ? 1 : last - 1; // } // // private void ApplyWaterSurface(int i, float surfaceY, bool startUnderWater, int startAdjacentIdx) // { // if (keepStartAdjacentNodeFollow && startUnderWater && i == startAdjacentIdx) // { // Vector3 s = _pCurr[0]; // _pCurr[i] = s; // _pPrev[i] = s; // return; // } // // Vector3 p = _pCurr[i]; // if (p.y < surfaceY) // { // p.y = Mathf.Lerp(p.y, surfaceY, waterLiftStrength); // _pCurr[i] = p; // // // 渐进同步 prev,削弱向下惯性,避免反复穿透水面 // Vector3 prev = _pPrev[i]; // if (prev.y < p.y) prev.y = Mathf.Lerp(prev.y, p.y, waterLiftStrength); // _pPrev[i] = prev; // } // } // // private void DrawHighResLine_Fast() // { // if (_pCurr == null || _physicsNodes < 2) return; // // float w = lineWidth * LineMultiple; // _lineRenderer.startWidth = w; // _lineRenderer.endWidth = w; // // if (!smooth) // { // _lineRenderer.positionCount = _physicsNodes; // _lineRenderer.SetPositions(_pCurr); // return; // } // // int subdiv = PickRenderSubdivisions_Fast(); // TCaches tc = (subdiv == renderSubdivisionsMoving) ? _tMoving : _tIdle; // // int needed = (_physicsNodes - 1) * subdiv + 1; // if (needed > _rCapacity) // { // _rCapacity = needed; // _rPoints = new Vector3[_rCapacity]; // } // // int idx = 0; // int last = _physicsNodes - 1; // // for (int seg = 0; seg < last; seg++) // { // int i0 = seg - 1; // if (i0 < 0) i0 = 0; // int i1 = seg; // int i2 = seg + 1; // int i3 = seg + 2; // if (i3 > last) i3 = last; // // Vector3 p0 = _pCurr[i0]; // Vector3 p1 = _pCurr[i1]; // Vector3 p2 = _pCurr[i2]; // Vector3 p3 = _pCurr[i3]; // // for (int s = 0; s < subdiv; s++) // { // float t = tc.t[s]; // float t2 = tc.t2[s]; // float t3 = tc.t3[s]; // // Vector3 cr = // 0.5f * ( // (2f * p1) + // (-p0 + p2) * t + // (2f * p0 - 5f * p1 + 4f * p2 - p3) * t2 + // (-p0 + 3f * p1 - 3f * p2 + p3) * t3 // ); // // // y 也使用平滑曲线,再做单调夹紧;避免垂直时因为线性 y 插值导致切线断裂,看起来像折线。 // cr.y = ClampMonotonic(cr.y, p0.y, p1.y, p2.y, p3.y); // // _rPoints[idx++] = cr; // } // } // // _rPoints[idx++] = _pCurr[last]; // // _lineRenderer.positionCount = idx; // _lineRenderer.SetPositions(_rPoints); // } // // private static float ClampMonotonic(float value, float p0, float p1, float p2, float p3) // { // bool rising = p0 <= p1 && p1 <= p2 && p2 <= p3; // bool falling = p0 >= p1 && p1 >= p2 && p2 >= p3; // if (!rising && !falling) // return value; // // float min = Mathf.Min(p1, p2); // float max = Mathf.Max(p1, p2); // return Mathf.Clamp(value, min, max); // } // // private int PickRenderSubdivisions_Fast() // { // int idle = Mathf.Max(1, renderSubdivisionsIdle); // int moving = Mathf.Max(1, renderSubdivisionsMoving); // // float thr = movingSpeedThreshold; // float thrSq = (thr * _dt) * (thr * _dt); // // float sumSq = 0f; // int count = Mathf.Max(1, _physicsNodes - 2); // // for (int i = 1; i < _physicsNodes - 1; i++) // { // Vector3 disp = _pCurr[i] - _pPrev[i]; // sumSq += disp.sqrMagnitude; // } // // float avgSq = sumSq / count; // // return (avgSq > thrSq) ? moving : idle; // } // // private static void BuildTCaches(int subdiv, ref TCaches caches) // { // subdiv = Mathf.Max(1, subdiv); // caches.t = new float[subdiv]; // caches.t2 = new float[subdiv]; // caches.t3 = new float[subdiv]; // // float inv = 1f / subdiv; // for (int s = 0; s < subdiv; s++) // { // float t = s * inv; // float t2 = t * t; // caches.t[s] = t; // caches.t2[s] = t2; // caches.t3[s] = t2 * t; // } // } // // private void OnDrawGizmosSelected() // { // if (_pCurr == null) return; // Gizmos.color = Color.yellow; // for (int i = 0; i < _physicsNodes; i++) // Gizmos.DrawSphere(_pCurr[i], 0.01f); // } // }