diff --git a/Assets/ResRaw/Prefabs/Line/fishing line float set.prefab b/Assets/ResRaw/Prefabs/Line/fishing line float set.prefab index d6cae03b2..0d7b0fb5e 100644 --- a/Assets/ResRaw/Prefabs/Line/fishing line float set.prefab +++ b/Assets/ResRaw/Prefabs/Line/fishing line float set.prefab @@ -47,7 +47,7 @@ MonoBehaviour: m_EditorClassIdentifier: startAnchor: {fileID: 0} endAnchor: {fileID: 54298866000586118} - LineMultiple: 5 + LineMultiple: 1 physicsSegmentLen: 0.1 minPhysicsNodes: 2 maxPhysicsNodes: 200 @@ -744,7 +744,7 @@ MonoBehaviour: m_EditorClassIdentifier: startAnchor: {fileID: 54298866000586118} endAnchor: {fileID: 54679398375713381} - LineMultiple: 5 + LineMultiple: 1 physicsSegmentLen: 0.2 minPhysicsNodes: 2 maxPhysicsNodes: 120 @@ -778,7 +778,7 @@ MonoBehaviour: renderSubdivisionsIdle: 6 renderSubdivisionsMoving: 2 movingSpeedThreshold: 2 - smooth: 1 + smooth: 0 lineWidth: 0.001 airDrag: 0.9 airDragXZ: 0.6 diff --git a/Assets/Scripts/Editor/RopeEditor.cs b/Assets/Scripts/Editor/RopeEditor.cs index 595601183..1f5082308 100644 --- a/Assets/Scripts/Editor/RopeEditor.cs +++ b/Assets/Scripts/Editor/RopeEditor.cs @@ -19,7 +19,8 @@ public class RopeFishLineEditor : Editor if (GUILayout.Button("打印总长度")) { - Debug.Log($"总长度={_target.GetCurrentLength()} 目标长度={_target.GetTargetLength()} smoot={_target.GetLengthSmoothVel()} relLen={_target.GetLengthByPoints()}"); + _target.DebugLength(); + // Debug.Log($"总长度={_target.GetCurrentLength()} 目标长度={_target.GetTargetLength()} smoot={_target.GetLengthSmoothVel()} relLen={_target.GetLengthByPoints()} PolylineLength={_target.GetPhysicsPolylineLength()}"); } // serializedObject.Update(); // EditorGUILayout.PropertyField(lookAtPoint); diff --git a/Assets/Scripts/Fishing/Rope/Rope.cs b/Assets/Scripts/Fishing/Rope/Rope.cs index 07a4f7e93..2c159cf28 100644 --- a/Assets/Scripts/Fishing/Rope/Rope.cs +++ b/Assets/Scripts/Fishing/Rope/Rope.cs @@ -284,6 +284,24 @@ public class Rope : MonoBehaviour 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() + { + Debug.Log( + $"current={_currentLength}, target={_targetLength}, nodes={_physicsNodes}, " + + $"seg={physicsSegmentLen}, head={_headRestLen}, headMin={headMinLen}, " + + $"solverRestTotal={(_physicsNodes - 2) * physicsSegmentLen + _headRestLen}, " + + $"poly={GetPhysicsPolylineLength()}" + ); + } private void FixedUpdate() { diff --git a/Assets/Scripts/Fishing/Rope/RopeBack b/Assets/Scripts/Fishing/Rope/RopeBack new file mode 100644 index 000000000..e37c49023 --- /dev/null +++ b/Assets/Scripts/Fishing/Rope/RopeBack @@ -0,0 +1,782 @@ +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; + + [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; + 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(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("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 FRod _rod; + public void Init(FRod rod) => _rod = rod; + + // 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); + + _startTr = startAnchor ? startAnchor.transform : null; + _endTr = endAnchor ? endAnchor.transform : null; + + InitLengthSystem(); + AllocateAndInitNodes(); + + int maxSubdiv = Mathf.Max(1, renderSubdivisionsIdle); + _rCapacity = (maxPhysicsNodes - 1) * maxSubdiv + 1; + _rPoints = new Vector3[_rCapacity]; + + BuildTCaches(renderSubdivisionsIdle, ref _tIdle); + BuildTCaches(renderSubdivisionsMoving, ref _tMoving); + } + + private void OnValidate() + { + renderSubdivisionsIdle = Mathf.Max(renderSubdivisionsIdle, 1); + renderSubdivisionsMoving = Mathf.Max(renderSubdivisionsMoving, 1); + iterations = Mathf.Clamp(iterations, 1, 80); + 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); + + waterSampleStep = Mathf.Max(1, waterSampleStep); + waterUpdateEvery = Mathf.Max(1, waterUpdateEvery); + waterSurfaceOffset = Mathf.Max(0f, waterSurfaceOffset); + waterPostConstraintIterations = Mathf.Clamp(waterPostConstraintIterations, 0, 8); + } + + 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.FloorToInt(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 (_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() + { + Debug.Log( + $"current={_currentLength}, target={_targetLength}, nodes={_physicsNodes}, " + + $"seg={physicsSegmentLen}, head={_headRestLen}, headMin={headMinLen}, " + + $"solverRestTotal={(_physicsNodes - 2) * physicsSegmentLen + _headRestLen}, " + + $"poly={GetPhysicsPolylineLength()}" + ); + } + + private void FixedUpdate() + { + if (!startAnchor || !endAnchor) 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(); + + LockAnchorsHard(); + + for (int it = 0; it < iterations; it++) + SolveDistanceConstraints_HeadOnly_Fast(); + + LockAnchorsHard(); + + if (constrainToGround) + { + _groundFrameCounter++; + if (_groundFrameCounter >= groundUpdateEvery) + { + _groundFrameCounter = 0; + ConstrainToGround(); + } + } + + if (constrainToWaterSurface) + { + _waterFrameCounter++; + if (_waterFrameCounter >= waterUpdateEvery) + { + _waterFrameCounter = 0; + ConstrainToWaterSurface(); + + // 水面抬升后补几次长度约束,让形状更顺一点 + for (int it = 0; it < waterPostConstraintIterations; it++) + SolveDistanceConstraints_HeadOnly_Fast(); + } + } + + LockAnchorsHard(); + } + + private void LateUpdate() + { + if (!startAnchor || !endAnchor || _pCurr == null || _physicsNodes < 2) return; + + int last = _physicsNodes - 1; + + Vector3 s = _startTr.position; + Vector3 e = _endTr.position; + + _pCurr[0] = s; + _pPrev[0] = s; + _pCurr[last] = e; + _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() + { + int last = _physicsNodes - 1; + + for (int i = 0; i < last; i++) + { + 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 * stiffness); + + if (i != 0) _pCurr[i] = a + corr * 0.5f; + if (i + 1 != last) _pCurr[i + 1] = b - corr * 0.5f; + } + } + + 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; + + int prevSampleIdx = 1; + float prevSurfaceY = surfaceY; + + ApplyWaterSurface(prevSampleIdx, prevSurfaceY); + + for (int i = 1 + step; i < last; i += step) + { + float nextSurfaceY = surfaceY; + ApplyWaterSurface(i, nextSurfaceY); + + 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); + } + } + else + { + for (int idx = prevSampleIdx + 1; idx < i; idx++) + ApplyWaterSurface(idx, prevSurfaceY); + } + + prevSampleIdx = i; + prevSurfaceY = nextSurfaceY; + } + + for (int i = prevSampleIdx + 1; i < last; i++) + ApplyWaterSurface(i, prevSurfaceY); + } + + private void ApplyWaterSurface(int i, float surfaceY) + { + Vector3 p = _pCurr[i]; + if (p.y < surfaceY) + { + p.y = surfaceY; + _pCurr[i] = p; + + // 同步 prev,杀掉向下惯性,避免反复穿透水面 + Vector3 prev = _pPrev[i]; + if (prev.y < surfaceY) prev.y = surfaceY; + _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 + ); + + cr.y = p1.y + (p2.y - p1.y) * t; + + _rPoints[idx++] = cr; + } + } + + _rPoints[idx++] = _pCurr[last]; + + _lineRenderer.positionCount = idx; + _lineRenderer.SetPositions(_rPoints); + } + + 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); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Fishing/Rope/RopeBack.meta b/Assets/Scripts/Fishing/Rope/RopeBack.meta new file mode 100644 index 000000000..70c81088d --- /dev/null +++ b/Assets/Scripts/Fishing/Rope/RopeBack.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d44eb051084541ebb5c082445b2111c0 +timeCreated: 1774151232 \ No newline at end of file