From b83dfd47b17adf054ba8351f6de348cd1f2f2929 Mon Sep 17 00:00:00 2001 From: "Bob.Song" <605277374@qq.com> Date: Sun, 12 Apr 2026 21:17:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=B1=BC=E7=BA=BF=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ResRaw/Prefabs/Line/FishingLine1.prefab | 8 +- Assets/Scripts/Editor/RopeEditor.cs | 58 +- Assets/Scripts/Fishing/LureController.cs | 118 +- .../New/View/FishingLine/FishingLineNode.cs | 2 +- .../New/View/FishingLine/FishingLineSolver.cs | 67 +- .../Player/States/IPlayerThrowAnimation.cs | 3 +- .../States/ParabolaPlayerThrowAnimation.cs | 10 +- .../View/Player/States/PlayerStageViewBase.cs | 16 +- .../Player/States/PlayerStageViewThrow.cs | 11 +- .../Fishing/New/View/Player/Tackle/FBobber.cs | 4 +- .../Fishing/New/View/Player/Tackle/FHook.cs | 4 +- .../Fishing/New/View/Player/Tackle/FLine.cs | 346 +-- .../Fishing/New/View/Player/Tackle/FLure.cs | 5 +- .../Fishing/New/View/Player/Tackle/FRod.cs | 45 +- Assets/Scripts/Fishing/Rope/Rope.cs | 2138 ++++++++--------- 15 files changed, 1446 insertions(+), 1389 deletions(-) diff --git a/Assets/ResRaw/Prefabs/Line/FishingLine1.prefab b/Assets/ResRaw/Prefabs/Line/FishingLine1.prefab index 626a6ca3f..0271c9ed5 100644 --- a/Assets/ResRaw/Prefabs/Line/FishingLine1.prefab +++ b/Assets/ResRaw/Prefabs/Line/FishingLine1.prefab @@ -42,7 +42,7 @@ Rigidbody: m_GameObject: {fileID: 2696931885206049402} serializedVersion: 5 m_Mass: 1 - m_LinearDamping: 0 + m_LinearDamping: 1 m_AngularDamping: 0.05 m_CenterOfMass: {x: 0, y: 0, z: 0} m_InertiaTensor: {x: 1, y: 1, z: 1} @@ -238,7 +238,7 @@ Rigidbody: m_GameObject: {fileID: 5252216124238432432} serializedVersion: 5 m_Mass: 1 - m_LinearDamping: 0 + m_LinearDamping: 1 m_AngularDamping: 0.05 m_CenterOfMass: {x: 0, y: 0, z: 0} m_InertiaTensor: {x: 1, y: 1, z: 1} @@ -407,6 +407,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: dcd0fd8d96f994444b2d8663af6b915d, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::NBF.FishingLineSolver + ConfigId: 0 + LineType: 0 anchorTransform: {fileID: 0} logicalNodes: - {fileID: 5572865435543895569} @@ -482,7 +484,7 @@ LineRenderer: m_Curve: - serializedVersion: 3 time: 0 - value: 0.01 + value: 0.002 inSlope: 0 outSlope: 0 tangentMode: 0 diff --git a/Assets/Scripts/Editor/RopeEditor.cs b/Assets/Scripts/Editor/RopeEditor.cs index 1f5082308..bf6fa7ce5 100644 --- a/Assets/Scripts/Editor/RopeEditor.cs +++ b/Assets/Scripts/Editor/RopeEditor.cs @@ -1,29 +1,29 @@ -using UnityEditor; -using UnityEngine; - -[CustomEditor(typeof(Rope))] -public class RopeFishLineEditor : Editor -{ - private Rope _target; - - void OnEnable() - { - _target = target as Rope; - // lookAtPoint = serializedObject.FindProperty("lookAtPoint"); - } - - public override void OnInspectorGUI() - { - base.OnInspectorGUI(); - - - if (GUILayout.Button("打印总长度")) - { - _target.DebugLength(); - // Debug.Log($"总长度={_target.GetCurrentLength()} 目标长度={_target.GetTargetLength()} smoot={_target.GetLengthSmoothVel()} relLen={_target.GetLengthByPoints()} PolylineLength={_target.GetPhysicsPolylineLength()}"); - } - // serializedObject.Update(); - // EditorGUILayout.PropertyField(lookAtPoint); - // serializedObject.ApplyModifiedProperties(); - } -} \ No newline at end of file +// using UnityEditor; +// using UnityEngine; +// +// [CustomEditor(typeof(Rope))] +// public class RopeFishLineEditor : Editor +// { +// private Rope _target; +// +// void OnEnable() +// { +// _target = target as Rope; +// // lookAtPoint = serializedObject.FindProperty("lookAtPoint"); +// } +// +// public override void OnInspectorGUI() +// { +// base.OnInspectorGUI(); +// +// +// if (GUILayout.Button("打印总长度")) +// { +// _target.DebugLength(); +// // Debug.Log($"总长度={_target.GetCurrentLength()} 目标长度={_target.GetTargetLength()} smoot={_target.GetLengthSmoothVel()} relLen={_target.GetLengthByPoints()} PolylineLength={_target.GetPhysicsPolylineLength()}"); +// } +// // serializedObject.Update(); +// // EditorGUILayout.PropertyField(lookAtPoint); +// // serializedObject.ApplyModifiedProperties(); +// } +// } \ No newline at end of file diff --git a/Assets/Scripts/Fishing/LureController.cs b/Assets/Scripts/Fishing/LureController.cs index 781f31158..eb9869ae2 100644 --- a/Assets/Scripts/Fishing/LureController.cs +++ b/Assets/Scripts/Fishing/LureController.cs @@ -1,59 +1,59 @@ -using System; -using UnityEngine; - -namespace NBF -{ - public class LureController : MonoBehaviour - { - [SerializeField] private Rigidbody rBody; - [SerializeField] private ConfigurableJoint joint; - public Rigidbody RBody => rBody; - - public ConfigurableJoint Joint => joint; - - private void Start() - { - RBody.detectCollisions = true; - RBody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic; - RBody.interpolation = RigidbodyInterpolation.Interpolate; - } - - public void SetJoint(Rigidbody rb) - { - joint.connectedBody = rb; - } - - - public void EnableCollision(bool enable) - { - if (rBody == null) - { - rBody = GetComponent(); - } - - // rBody.detectCollisions = enable; - } - - public void SetKinematic(bool value) - { - rBody.isKinematic = value; - } - - public void SetJointDistance(float limit) - { - joint.linearLimit = new SoftJointLimit - { - limit = limit - }; - } - - private void OnCollisionEnter(Collision other) - { - Debug.Log($"OnCollisionEnter:{other.gameObject.name}"); - } - private void OnCollisionExit(Collision other) - { - Debug.Log($"OnCollisionExit:{other.gameObject.name}"); - } - } -} \ No newline at end of file +// using System; +// using UnityEngine; +// +// namespace NBF +// { +// public class LureController : MonoBehaviour +// { +// [SerializeField] private Rigidbody rBody; +// [SerializeField] private ConfigurableJoint joint; +// public Rigidbody RBody => rBody; +// +// public ConfigurableJoint Joint => joint; +// +// private void Start() +// { +// RBody.detectCollisions = true; +// RBody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic; +// RBody.interpolation = RigidbodyInterpolation.Interpolate; +// } +// +// public void SetJoint(Rigidbody rb) +// { +// joint.connectedBody = rb; +// } +// +// +// public void EnableCollision(bool enable) +// { +// if (rBody == null) +// { +// rBody = GetComponent(); +// } +// +// // rBody.detectCollisions = enable; +// } +// +// public void SetKinematic(bool value) +// { +// rBody.isKinematic = value; +// } +// +// public void SetJointDistance(float limit) +// { +// joint.linearLimit = new SoftJointLimit +// { +// limit = limit +// }; +// } +// +// private void OnCollisionEnter(Collision other) +// { +// Debug.Log($"OnCollisionEnter:{other.gameObject.name}"); +// } +// private void OnCollisionExit(Collision other) +// { +// Debug.Log($"OnCollisionExit:{other.gameObject.name}"); +// } +// } +// } \ No newline at end of file diff --git a/Assets/Scripts/Fishing/New/View/FishingLine/FishingLineNode.cs b/Assets/Scripts/Fishing/New/View/FishingLine/FishingLineNode.cs index 588e58dc4..fa4365ff3 100644 --- a/Assets/Scripts/Fishing/New/View/FishingLine/FishingLineNode.cs +++ b/Assets/Scripts/Fishing/New/View/FishingLine/FishingLineNode.cs @@ -18,7 +18,7 @@ namespace NBF private FishingLineSolver _solver; [Header("Node")] [SerializeField] private NodeType nodeType = NodeType.Tail; - [SerializeField] private Rigidbody body; + [SerializeField] public Rigidbody body; [SerializeField] private MonoBehaviour interaction; [Header("Segment To Next Logical Node")] [Min(0f)] [SerializeField] diff --git a/Assets/Scripts/Fishing/New/View/FishingLine/FishingLineSolver.cs b/Assets/Scripts/Fishing/New/View/FishingLine/FishingLineSolver.cs index ccaddb955..f2bd14984 100644 --- a/Assets/Scripts/Fishing/New/View/FishingLine/FishingLineSolver.cs +++ b/Assets/Scripts/Fishing/New/View/FishingLine/FishingLineSolver.cs @@ -5,7 +5,15 @@ using UnityEngine; namespace NBF { - public class FishingLineSolver : MonoBehaviour + public enum LineType + { + Hand, + HandDouble, + Spinning, + SpinningFloat, + } + + public class FishingLineSolver : FGearBase { [Serializable] public sealed class ChainPoint @@ -36,6 +44,9 @@ namespace NBF public int VirtualNodeCount => Mathf.Max(0, GapLengths.Length - 1); } + + [SerializeField] public LineType LineType; + [Header("References")] [SerializeField] private Transform anchorTransform; @@ -202,6 +213,12 @@ namespace NBF } } + protected override void OnInit() + { + var tipRb = Rod.Asset.LineConnectorRigidbody; + anchorTransform = tipRb.transform; + } + private void Start() { if (autoBuildOnStart) @@ -312,28 +329,46 @@ namespace NBF } /// - /// 获取指定顺序索引的逻辑节点。 - /// 索引基于 logicalNodes 配置顺序;超出范围或节点为空时返回 null。 + /// 根据类型获取逻辑节点类型 /// - public FishingLineNode GetLogicalNode(int logicalIndex) + /// + /// + public FishingLineNode GetLogicalNode(FishingLineNode.NodeType nodeType) { - if (logicalNodes == null || logicalIndex < 0 || logicalIndex >= logicalNodes.Length) + foreach (var fishingLineNode in logicalNodes) { - return null; + if (fishingLineNode.Type == nodeType) + { + return fishingLineNode; + } } - return logicalNodes[logicalIndex]; + return null; } - /// - /// 尝试获取指定顺序索引的逻辑节点。 - /// 获取失败时返回 false,并将 node 置为 null。 - /// - public bool TryGetLogicalNode(int logicalIndex, out FishingLineNode node) - { - node = GetLogicalNode(logicalIndex); - return node != null; - } + // /// + // /// 获取指定顺序索引的逻辑节点。 + // /// 索引基于 logicalNodes 配置顺序;超出范围或节点为空时返回 null。 + // /// + // public FishingLineNode GetLogicalNode(int logicalIndex) + // { + // if (logicalNodes == null || logicalIndex < 0 || logicalIndex >= logicalNodes.Length) + // { + // return null; + // } + // + // return logicalNodes[logicalIndex]; + // } + // + // /// + // /// 尝试获取指定顺序索引的逻辑节点。 + // /// 获取失败时返回 false,并将 node 置为 null。 + // /// + // public bool TryGetLogicalNode(int logicalIndex, out FishingLineNode node) + // { + // node = GetLogicalNode(logicalIndex); + // return node != null; + // } /// /// 获取当前起点逻辑节点。 diff --git a/Assets/Scripts/Fishing/New/View/Player/States/IPlayerThrowAnimation.cs b/Assets/Scripts/Fishing/New/View/Player/States/IPlayerThrowAnimation.cs index 06a3a7ee7..86e8571f8 100644 --- a/Assets/Scripts/Fishing/New/View/Player/States/IPlayerThrowAnimation.cs +++ b/Assets/Scripts/Fishing/New/View/Player/States/IPlayerThrowAnimation.cs @@ -13,7 +13,8 @@ namespace NBF public struct ThrowAnimationRequest { - public LureController Lure; + // public LureController Lure; + public FishingLineNode EndNode; public Vector3 ThrowOriginPosition; public Vector3 StartPosition; public Vector3 Forward; diff --git a/Assets/Scripts/Fishing/New/View/Player/States/ParabolaPlayerThrowAnimation.cs b/Assets/Scripts/Fishing/New/View/Player/States/ParabolaPlayerThrowAnimation.cs index 1d0195944..7075bec98 100644 --- a/Assets/Scripts/Fishing/New/View/Player/States/ParabolaPlayerThrowAnimation.cs +++ b/Assets/Scripts/Fishing/New/View/Player/States/ParabolaPlayerThrowAnimation.cs @@ -22,7 +22,7 @@ namespace NBF private float _castElapsedTime; private Vector3 _castStartPos; private Vector3 _castTargetPos; - private LureController _castingLure; + private FishingLineNode _castingLure; public bool IsPlaying => _castingLure != null; @@ -40,18 +40,18 @@ namespace NBF public void Play(ThrowAnimationRequest request) { - if (request.Lure == null) + if (request.EndNode == null) { return; } Stop(snapToTarget: false); - _castingLure = request.Lure; + _castingLure = request.EndNode; _chargedProgress = Mathf.Clamp01(request.ChargedProgress); _castElapsedTime = 0f; - var lureBody = request.Lure.RBody; + var lureBody = request.EndNode.body; _castStartPos = request.StartPosition; Vector3 forward = GetHorizontalForward(request.Forward); @@ -81,7 +81,7 @@ namespace NBF return; } - var lureBody = _castingLure.RBody; + var lureBody = _castingLure.body; if (snapToTarget) { _castingLure.transform.position = _castTargetPos; diff --git a/Assets/Scripts/Fishing/New/View/Player/States/PlayerStageViewBase.cs b/Assets/Scripts/Fishing/New/View/Player/States/PlayerStageViewBase.cs index 98d5c68b9..396ec9daf 100644 --- a/Assets/Scripts/Fishing/New/View/Player/States/PlayerStageViewBase.cs +++ b/Assets/Scripts/Fishing/New/View/Player/States/PlayerStageViewBase.cs @@ -101,10 +101,10 @@ namespace NBF var handItemView = Player.HandItem.GetComponent(); if (handItemView != null && handItemView.Rod != null) { - if (handItemView.Rod.Line.PinchController != null) - { - handItemView.Rod.Line.PinchController.StartPinch(view.Unity.ModelAsset.Pinch); - } + // if (handItemView.Rod.Line.PinchController != null) + // { + // handItemView.Rod.Line.PinchController.StartPinch(view.Unity.ModelAsset.Pinch); + // } } } } @@ -118,10 +118,10 @@ namespace NBF var handItemView = Player.HandItem.GetComponent(); if (handItemView != null && handItemView.Rod != null) { - if (handItemView.Rod.Line.PinchController != null) - { - handItemView.Rod.Line.PinchController.ReleasePinch(); - } + // if (handItemView.Rod.Line.PinchController != null) + // { + // handItemView.Rod.Line.PinchController.ReleasePinch(); + // } } } } diff --git a/Assets/Scripts/Fishing/New/View/Player/States/PlayerStageViewThrow.cs b/Assets/Scripts/Fishing/New/View/Player/States/PlayerStageViewThrow.cs index a47e9c474..059ae911d 100644 --- a/Assets/Scripts/Fishing/New/View/Player/States/PlayerStageViewThrow.cs +++ b/Assets/Scripts/Fishing/New/View/Player/States/PlayerStageViewThrow.cs @@ -61,7 +61,12 @@ namespace NBF PlayerView.Unity.ModelAsset.PlayerAnimator.StartThrow = false; var rod = GetRod(); - if (rod == null || rod.Line == null || rod.Line.Lure == null) + if (rod == null || rod.Line == null ) + { + return; + } + var endNode = rod.Line.GetEndNode(); + if (endNode == null) { return; } @@ -70,9 +75,9 @@ namespace NBF _throwAnimation.Player = Player; _throwAnimation?.Play(new ThrowAnimationRequest { - Lure = rod.Line.Lure, + EndNode = endNode, ThrowOriginPosition = PlayerView.Unity.transform.position, - StartPosition = rod.Line.Lure.RBody.position, + StartPosition = endNode.body.position, Forward = PlayerView.Unity.transform.forward, ChargedProgress = ChargedProgress }); diff --git a/Assets/Scripts/Fishing/New/View/Player/Tackle/FBobber.cs b/Assets/Scripts/Fishing/New/View/Player/Tackle/FBobber.cs index 72600e90f..72e3561db 100644 --- a/Assets/Scripts/Fishing/New/View/Player/Tackle/FBobber.cs +++ b/Assets/Scripts/Fishing/New/View/Player/Tackle/FBobber.cs @@ -7,7 +7,9 @@ namespace NBF protected override void OnInit() { // transform.position = Rod.lineHandler.LineConnector_1.transform.position; - SetParent(Rod.Line.Bobber.transform); + + var node = Rod.Line.GetLogicalNode(FishingLineNode.NodeType.Float); + SetParent(node.transform); transform.localPosition = Vector3.zero; // var buoyancy = GetComponentInParent(); // buoyancy.InitBobber(); diff --git a/Assets/Scripts/Fishing/New/View/Player/Tackle/FHook.cs b/Assets/Scripts/Fishing/New/View/Player/Tackle/FHook.cs index 777d046c3..920864571 100644 --- a/Assets/Scripts/Fishing/New/View/Player/Tackle/FHook.cs +++ b/Assets/Scripts/Fishing/New/View/Player/Tackle/FHook.cs @@ -18,8 +18,10 @@ namespace NBF // transform.rotation = Rod.lineHandler.LineConnector_2.transform.rotation; // 确保旋转也同步 // SetParent(Rod.lineHandler.LineConnector_2.transform); + var node = Rod.Line.GetLogicalNode(FishingLineNode.NodeType.Tail); + SetParent(node.transform); - SetParent(Rod.Line.Lure.transform); + // SetParent(Rod.Line.Lure.transform); transform.localPosition = Vector3.zero; // var target = lineHandler.LineConnector_2.GetComponent(); diff --git a/Assets/Scripts/Fishing/New/View/Player/Tackle/FLine.cs b/Assets/Scripts/Fishing/New/View/Player/Tackle/FLine.cs index 0dd442155..43c828278 100644 --- a/Assets/Scripts/Fishing/New/View/Player/Tackle/FLine.cs +++ b/Assets/Scripts/Fishing/New/View/Player/Tackle/FLine.cs @@ -1,173 +1,173 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using NBC; -// using Obi; -using UnityEngine; - -namespace NBF -{ - public enum LineType - { - Hand, - HandDouble, - Spinning, - SpinningFloat, - } - - public class FLine : FGearBase - { - public LineType LineType; - - [SerializeField] private bool isLureConnect; - [SerializeField] private RodLine rodLine; - - /// - /// 主线 - /// - [SerializeField] private Rope fishingRope; - - /// - /// 浮漂和鱼钩线 - /// - [SerializeField] private Rope bobberRope; - - public LureController Lure; - public BobberController Bobber; - - public JointPinchController PinchController; - - - public float LinelenghtDiferent; - - protected override void OnInit() - { - var tipRb = Rod.Asset.LineConnectorRigidbody; - if (isLureConnect) - { - Lure.SetJoint(tipRb); - Lure.EnableCollision(false); - } - else - { - fishingRope.startAnchor = tipRb; - Bobber.SetJoint(tipRb); - Lure.SetJoint(Bobber.rbody); - Lure.gameObject.SetActive(true); - Lure.EnableCollision(false); - Lure.SetKinematic(false); - } - - GetComponentsInChildren(includeInactive: true).ToList().ForEach(delegate(Transform i) - { - i.gameObject.SetActive(true); - }); - - StartCoroutine(LureUseGravity()); - if (isLureConnect) - { - fishingRope.Init(Rod); - } - else - { - fishingRope.Init(Rod); - bobberRope.Init(Rod); - } - - // rodLine.GenerateLineRendererRope(guides.ToArray(), _LineThickness); - } - - public void InitTest(Rigidbody tipRb) - { - if (isLureConnect) - { - Lure.SetJoint(tipRb); - Lure.EnableCollision(false); - } - else - { - fishingRope.startAnchor = tipRb; - Bobber.SetJoint(tipRb); - Lure.SetJoint(Bobber.rbody); - Lure.gameObject.SetActive(true); - Lure.EnableCollision(false); - Lure.SetKinematic(false); - } - - GetComponentsInChildren(includeInactive: true).ToList().ForEach(delegate(Transform i) - { - i.gameObject.SetActive(true); - }); - - StartCoroutine(LureUseGravity()); - if (isLureConnect) - { - fishingRope.Init(Rod); - } - else - { - fishingRope.Init(Rod); - bobberRope.Init(Rod); - } - } - - private IEnumerator LureUseGravity() - { - yield return 1; - Lure.gameObject.SetActive(false); - Lure.gameObject.SetActive(true); - yield return 1; - Lure.RBody.useGravity = true; - } - - public void SetTargetLength(float value) - { - Log.Error($"SetObiRopeStretch={value}"); - if (value > 3) - { - // value -= 0.2f; - } - - fishingRope.SetTargetLength(value); - } - - public void SetLureLength(float value) - { - Log.Error($"SetObiRopeStretch={value}"); - bobberRope.SetTargetLength(value); - } - - - private void Update() - { - LinelenghtDiferent = GetLineDistance(); - - //非钓鱼状态 - Rod.PlayerItem.Tension = Mathf.Clamp(LinelenghtDiferent, 0f, 0.05f); - } - - #region Tension - - private float GetLineDistance() - { - if (!Bobber.JointRb) - { - return 0; - } - - // return 0; - - //第一个节点到竿稍的位置-第一段鱼线长度 - return Vector3.Distance(Bobber.transform.position, Bobber.JointRb.transform.position) - - fishingRope.GetCurrentLength(); - } - - public float GetTension(float weight) - { - return weight * GetLineDistance(); - } - - #endregion - } -} \ No newline at end of file +// using System; +// using System.Collections; +// using System.Collections.Generic; +// using System.Linq; +// using NBC; +// // using Obi; +// using UnityEngine; +// +// namespace NBF +// { +// public enum LineType +// { +// Hand, +// HandDouble, +// Spinning, +// SpinningFloat, +// } +// +// public class FLine : FGearBase +// { +// public LineType LineType; +// +// [SerializeField] private bool isLureConnect; +// [SerializeField] private RodLine rodLine; +// +// /// +// /// 主线 +// /// +// [SerializeField] private Rope fishingRope; +// +// /// +// /// 浮漂和鱼钩线 +// /// +// [SerializeField] private Rope bobberRope; +// +// public LureController Lure; +// public BobberController Bobber; +// +// public JointPinchController PinchController; +// +// +// public float LinelenghtDiferent; +// +// protected override void OnInit() +// { +// var tipRb = Rod.Asset.LineConnectorRigidbody; +// if (isLureConnect) +// { +// Lure.SetJoint(tipRb); +// Lure.EnableCollision(false); +// } +// else +// { +// fishingRope.startAnchor = tipRb; +// Bobber.SetJoint(tipRb); +// Lure.SetJoint(Bobber.rbody); +// Lure.gameObject.SetActive(true); +// Lure.EnableCollision(false); +// Lure.SetKinematic(false); +// } +// +// GetComponentsInChildren(includeInactive: true).ToList().ForEach(delegate(Transform i) +// { +// i.gameObject.SetActive(true); +// }); +// +// StartCoroutine(LureUseGravity()); +// if (isLureConnect) +// { +// fishingRope.Init(Rod); +// } +// else +// { +// fishingRope.Init(Rod); +// bobberRope.Init(Rod); +// } +// +// // rodLine.GenerateLineRendererRope(guides.ToArray(), _LineThickness); +// } +// +// public void InitTest(Rigidbody tipRb) +// { +// if (isLureConnect) +// { +// Lure.SetJoint(tipRb); +// Lure.EnableCollision(false); +// } +// else +// { +// fishingRope.startAnchor = tipRb; +// Bobber.SetJoint(tipRb); +// Lure.SetJoint(Bobber.rbody); +// Lure.gameObject.SetActive(true); +// Lure.EnableCollision(false); +// Lure.SetKinematic(false); +// } +// +// GetComponentsInChildren(includeInactive: true).ToList().ForEach(delegate(Transform i) +// { +// i.gameObject.SetActive(true); +// }); +// +// StartCoroutine(LureUseGravity()); +// if (isLureConnect) +// { +// fishingRope.Init(Rod); +// } +// else +// { +// fishingRope.Init(Rod); +// bobberRope.Init(Rod); +// } +// } +// +// private IEnumerator LureUseGravity() +// { +// yield return 1; +// Lure.gameObject.SetActive(false); +// Lure.gameObject.SetActive(true); +// yield return 1; +// Lure.RBody.useGravity = true; +// } +// +// public void SetTargetLength(float value) +// { +// Log.Error($"SetObiRopeStretch={value}"); +// if (value > 3) +// { +// // value -= 0.2f; +// } +// +// fishingRope.SetTargetLength(value); +// } +// +// public void SetLureLength(float value) +// { +// Log.Error($"SetObiRopeStretch={value}"); +// bobberRope.SetTargetLength(value); +// } +// +// +// private void Update() +// { +// LinelenghtDiferent = GetLineDistance(); +// +// //非钓鱼状态 +// Rod.PlayerItem.Tension = Mathf.Clamp(LinelenghtDiferent, 0f, 0.05f); +// } +// +// #region Tension +// +// private float GetLineDistance() +// { +// if (!Bobber.JointRb) +// { +// return 0; +// } +// +// // return 0; +// +// //第一个节点到竿稍的位置-第一段鱼线长度 +// return Vector3.Distance(Bobber.transform.position, Bobber.JointRb.transform.position) - +// fishingRope.GetCurrentLength(); +// } +// +// public float GetTension(float weight) +// { +// return weight * GetLineDistance(); +// } +// +// #endregion +// } +// } \ No newline at end of file diff --git a/Assets/Scripts/Fishing/New/View/Player/Tackle/FLure.cs b/Assets/Scripts/Fishing/New/View/Player/Tackle/FLure.cs index fa7af1814..00780a9e9 100644 --- a/Assets/Scripts/Fishing/New/View/Player/Tackle/FLure.cs +++ b/Assets/Scripts/Fishing/New/View/Player/Tackle/FLure.cs @@ -14,7 +14,10 @@ namespace NBF // SetParent(Rod.lineHandler.LineConnector_1.transform); - SetParent(Rod.Line.Lure.transform); + var node = Rod.Line.GetLogicalNode(FishingLineNode.NodeType.Float); + SetParent(node.transform); + + // SetParent(Rod.Line.Lure.transform); transform.localPosition = Vector3.zero; } } diff --git a/Assets/Scripts/Fishing/New/View/Player/Tackle/FRod.cs b/Assets/Scripts/Fishing/New/View/Player/Tackle/FRod.cs index eef8eff7b..b2bb4d2ae 100644 --- a/Assets/Scripts/Fishing/New/View/Player/Tackle/FRod.cs +++ b/Assets/Scripts/Fishing/New/View/Player/Tackle/FRod.cs @@ -25,7 +25,7 @@ namespace NBF public FBait Bait; public FLure Lure; public FWeight Weight; - public FLine Line; + public FishingLineSolver Line; public Transform GearRoot; @@ -72,24 +72,29 @@ namespace NBF if (Line.LineType == LineType.Spinning) { //没有浮漂类型 - Line.Lure.SetJointDistance(PlayerItem.LineLength); - if (PlayerItem.StretchRope) - { - // Line.SetTargetLength(PlayerItem.Tension > 0f ? 0f : PlayerItem.LineLength); - Line.SetTargetLength(PlayerItem.LineLength); - } + // Line.Lure.SetJointDistance(PlayerItem.LineLength); + // if (PlayerItem.StretchRope) + // { + // // Line.SetTargetLength(PlayerItem.Tension > 0f ? 0f : PlayerItem.LineLength); + // Line.SetTargetLength(PlayerItem.LineLength); + // } + + Line.SetLenght(PlayerItem.LineLength); } else { //有浮漂 - Line.Lure.SetJointDistance(PlayerItem.FloatLength); - Line.Bobber.SetJointDistance(PlayerItem.LineLength - PlayerItem.FloatLength); - if (PlayerItem.StretchRope) - { - // Line.SetTargetLength(PlayerItem.Tension > 0f ? 0f : PlayerItem.LineLength - PlayerItem.FloatLength); - Line.SetTargetLength(PlayerItem.LineLength - PlayerItem.FloatLength); - Line.SetLureLength(PlayerItem.FloatLength); - } + // Line.Lure.SetJointDistance(PlayerItem.FloatLength); + // Line.Bobber.SetJointDistance(PlayerItem.LineLength - PlayerItem.FloatLength); + // if (PlayerItem.StretchRope) + // { + // // Line.SetTargetLength(PlayerItem.Tension > 0f ? 0f : PlayerItem.LineLength - PlayerItem.FloatLength); + // Line.SetTargetLength(PlayerItem.LineLength - PlayerItem.FloatLength); + // Line.SetLureLength(PlayerItem.FloatLength); + // } + + Line.SetLenght(PlayerItem.LineLength - PlayerItem.FloatLength); + Line.SetLenght(PlayerItem.FloatLength, 1); } } @@ -251,7 +256,7 @@ namespace NBF var solver = Instantiate(lineSolverPrefab, GearRoot); solver.transform.position = Asset.lineConnector.position; solver.transform.rotation = Asset.lineConnector.rotation; - var indexNames = new[] { "fishing line float set", "fishing line spinning" }; + var indexNames = new[] { "FishingLine1", "FishingLine1" }; var path = $"Assets/ResRaw/Prefabs/Line/{indexNames[currentLineTypeIndex]}.prefab"; var prefab = Assets.Load(path); @@ -261,7 +266,7 @@ namespace NBF obj.transform.localScale = Vector3.one; obj.transform.rotation = Quaternion.identity; - Line = obj.GetComponent(); + Line = obj.GetComponent(); Line.transform.position = Asset.lineConnector.position; Line.Init(this); } @@ -339,11 +344,13 @@ namespace NBF var state = PlayerItem.Owner.State; - Vector3 vector = Line.Lure.transform.position; + var endNode = Line.GetEndNode(); + + Vector3 vector = endNode.transform.position; // 当前物体的朝向与指向 Lure 的方向之间的夹角,在 0(完全对齐)到 1(完全相反)之间的一个比例值 float headingAlignment = Vector3.Angle(base.transform.forward, - (Line.Lure.transform.position - transform.position).normalized) / 180f; + (endNode.transform.position - transform.position).normalized) / 180f; // 经过朝向调制后的有效张力 var effectiveTension = Mathf.Clamp(CurrentTension01 * headingAlignment, 0f, 1f); diff --git a/Assets/Scripts/Fishing/Rope/Rope.cs b/Assets/Scripts/Fishing/Rope/Rope.cs index 03a9cf3a8..3bd8a0f25 100644 --- a/Assets/Scripts/Fishing/Rope/Rope.cs +++ b/Assets/Scripts/Fishing/Rope/Rope.cs @@ -1,1069 +1,1069 @@ -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); - } -} +// 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); +// } +// }