Files
Fishing2/Assets/Scripts/Fishing/New/View/FishingLine/FishingLineSolver.cs
2026-04-12 21:45:36 +08:00

963 lines
31 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
namespace NBF
{
public enum LineType
{
Hand,
HandDouble,
Spinning,
SpinningFloat,
}
public class FishingLineSolver : FGearBase
{
[Serializable]
public sealed class ChainPoint
{
public long Key;
public Vector3 Position;
public bool IsLogical;
public FishingLineNode LogicalNode;
public int SegmentIndex;
public int StableIndex;
public string GetDebugName()
{
if (IsLogical)
{
return LogicalNode != null ? LogicalNode.GetDebugName() : $"L[{StableIndex}]";
}
return $"V[S{SegmentIndex}:{StableIndex}]";
}
}
[Serializable]
private struct SegmentLayout
{
public float[] GapLengths;
public int VirtualNodeCount => Mathf.Max(0, GapLengths.Length - 1);
}
[SerializeField] public LineType LineType;
public JointPinchController PinchController;
[Header("References")] [SerializeField]
private Transform anchorTransform;
[SerializeField] private FishingLineNode[] logicalNodes = Array.Empty<FishingLineNode>();
[SerializeField] private FishingLineRenderer lineRenderer;
[Header("First Segment")] [Min(0f)] [SerializeField]
private float firstSegmentLength = 1.2f;
[Min(0.001f)] [SerializeField] private float firstSegmentStep = 0.1f;
[Header("Joint")] [Min(1)] [SerializeField]
private int jointSolverIterations = 12;
[SerializeField] private float jointProjectionDistance = 0.02f;
[SerializeField] private float jointProjectionAngle = 1f;
[Header("Limit Detection")]
[Min(0f)]
// 极限判定的长度容差,允许链路在总长或单段长度上存在少量误差。
[SerializeField]
private float lengthLimitTolerance = 0.01f;
[Min(0f)]
// 达到极限后,只有当前超长值大于该阈值时,才开始进入断线候选计时。
[SerializeField]
private float breakStretchThreshold = 0.05f;
[Min(0f)]
// 断线候选状态允许持续的最大时间;超过后会发出一次断线消息。
[SerializeField]
private float breakLimitDuration = 0.15f;
[Header("Runtime Debug")] [SerializeField]
private bool autoBuildOnStart = true;
private readonly List<ChainPoint> chainPoints = new();
private readonly List<float> restLengths = new();
private readonly List<int> pinnedIndices = new();
private readonly List<ConfigurableJoint> runtimeJoints = new();
private bool chainDirty = true;
private int runtimeVirtualPointCount;
public float FirstSegmentLength => firstSegmentLength;
public float FirstSegmentStep => firstSegmentStep;
public IReadOnlyList<ChainPoint> ChainPoints => chainPoints;
public IReadOnlyList<float> RestLengths => restLengths;
public IReadOnlyList<int> PinnedIndices => pinnedIndices;
/// <summary>
/// 当前配置的逻辑节点只读列表。
/// 外部可读取节点顺序,但不应直接修改数组内容。
/// </summary>
public IReadOnlyList<FishingLineNode> LogicalNodes => logicalNodes;
/// <summary>
/// 当前整条鱼线的配置总长度,等于所有段的静止长度之和。
/// </summary>
public float TotalLineLength { get; private set; }
/// <summary>
/// 当前逻辑节点链路的实际总长度,按相邻逻辑节点的实际距离累加。
/// </summary>
public float CurrentLogicalChainLength { get; private set; }
/// <summary>
/// 当前首尾逻辑节点之间的直线距离,仅作为端点跨度观察值。
/// </summary>
public float CurrentEndpointDistance { get; private set; }
/// <summary>
/// 当前逻辑链总长度超出配置总长度的部分,小于等于零时记为 0。
/// </summary>
public float CurrentStretchLength { get; private set; }
/// <summary>
/// 当前所有逻辑段中,单段超出配置长度的最大值。
/// </summary>
public float MaxSegmentStretchLength { get; private set; }
/// <summary>
/// 当前超限最明显的逻辑段索引;为 -1 表示没有段处于超限。
/// </summary>
public int MaxOverstretchedSegmentIndex { get; private set; } = -1;
/// <summary>
/// 当前受拉比例。
/// 该值取“单段实际长度 / 单段配置长度”和“整链实际长度 / 整链配置长度”中的最大值。
/// 约等于 1 表示接近拉直,大于 1 表示已经出现超限拉伸。
/// </summary>
public float CurrentTensionRatio { get; private set; }
/// <summary>
/// 断线候选拉伸阈值。
/// 只有当前处于极限状态,且超长值大于该阈值时,才会开始累计断线计时。
/// </summary>
public float BreakStretchThreshold => breakStretchThreshold;
/// <summary>
/// 断线候选状态可持续的最大时间。
/// 当断线计时超过该值时,会发出一次断线消息。
/// </summary>
public float BreakLimitDuration => breakLimitDuration;
/// <summary>
/// 当前拉力极限百分比。
/// 当超长值小于等于 lengthLimitTolerance 时为 0
/// 当超长值大于等于 breakStretchThreshold 时为 100
/// 中间区间按线性比例映射,供 UI 显示使用。
/// </summary>
public float CurrentBreakStretchPercent => EvaluateBreakStretchPercent(CurrentStretchLength);
/// <summary>
/// 当前是否处于极限状态。
/// 只要整链超出总长度容差,或任一逻辑段超出单段容差,即认为到达极限。
/// </summary>
public bool IsAtLimit { get; private set; }
/// <summary>
/// 当前断线候选状态的累计时间。
/// 只有在处于极限状态,且 CurrentStretchLength 大于断线阈值时才会累加;否则重置为 0。
/// </summary>
public float LimitStateTime { get; private set; }
/// <summary>
/// 当前是否正在进行断线候选计时。
/// </summary>
public bool IsBreakCountdownActive => IsAtLimit && CurrentStretchLength > breakStretchThreshold;
/// <summary>
/// 当前极限断线消息是否已经发出过。
/// 在退出断线候选状态前只会发一次,避免重复通知。
/// </summary>
public bool HasBreakNotificationSent { get; private set; }
/// <summary>
/// 当鱼线达到断线条件时发出的一次性消息。
/// 外部可订阅该事件,在回调中执行切线、播放表现或状态切换。
/// </summary>
public event Action<FishingLineSolver> OnLineBreakRequested;
public int LogicalNodeCount => logicalNodes?.Length ?? 0;
public int RuntimeVirtualNodeCount => runtimeVirtualPointCount;
public int ActiveRuntimeVirtualNodeCount => runtimeVirtualPointCount;
public int OrderedNodeCount => chainPoints.Count;
public int SegmentCount => restLengths.Count;
public bool IsChainDirty => chainDirty;
private void Reset()
{
if (lineRenderer == null)
{
TryGetComponent(out lineRenderer);
}
}
protected override void OnInit()
{
var tipRb = Rod.Asset.LineConnectorRigidbody;
anchorTransform = tipRb.transform;
}
private void Start()
{
if (autoBuildOnStart)
{
BuildLine();
}
}
private void FixedUpdate()
{
if (logicalNodes == null || logicalNodes.Length == 0)
{
ResetLimitState();
return;
}
UpdateAnchorNode();
if (chainDirty)
{
RebuildRuntimeChain();
}
EvaluateLimitState(Time.fixedDeltaTime);
}
private void LateUpdate()
{
if (chainDirty)
{
RebuildRuntimeChain();
}
SyncLogicalPointPositions();
if (lineRenderer != null && chainPoints.Count > 1)
{
lineRenderer.Render(this, Time.deltaTime);
}
}
private void OnValidate()
{
firstSegmentLength = Mathf.Max(0f, firstSegmentLength);
firstSegmentStep = Mathf.Max(0.001f, firstSegmentStep);
jointSolverIterations = Mathf.Max(1, jointSolverIterations);
lengthLimitTolerance = Mathf.Max(0f, lengthLimitTolerance);
breakStretchThreshold = Mathf.Max(0f, breakStretchThreshold);
breakLimitDuration = Mathf.Max(0f, breakLimitDuration);
chainDirty = true;
}
/// <summary>
/// 按当前配置重建整条鱼线的运行时链路,并立即刷新极限状态。
/// </summary>
[ContextMenu("Build Line")]
public void BuildLine()
{
ConfigureStartNode();
ConfigureLogicalJoints();
RebuildRuntimeChain();
EvaluateLimitState(0f);
}
/// <summary>
/// 设置指定逻辑段的配置长度。
/// segmentIndex 为 0 时表示第一段;大于 0 时表示对应逻辑节点到下一个逻辑节点的线长。
/// </summary>
public void SetLenght(float length, int segmentIndex = 0)
{
var clamped = Mathf.Max(0f, length);
var currentLength = GetSegmentLength(segmentIndex);
if (Mathf.Approximately(clamped, currentLength))
{
return;
}
if (segmentIndex <= 0)
{
firstSegmentLength = clamped;
}
else
{
if (logicalNodes == null || segmentIndex >= logicalNodes.Length)
{
return;
}
var sourceNode = logicalNodes[segmentIndex];
if (sourceNode == null)
{
return;
}
sourceNode.SegmentLengthToNext = clamped;
}
UpdateJointLimit(segmentIndex + 1, clamped);
chainDirty = true;
}
/// <summary>
/// 立即按当前配置重建鱼线。
/// </summary>
public void RebuildNow()
{
BuildLine();
}
/// <summary>
/// 根据类型获取逻辑节点类型
/// </summary>
/// <param name="nodeType"></param>
/// <returns></returns>
public FishingLineNode GetLogicalNode(FishingLineNode.NodeType nodeType)
{
foreach (var fishingLineNode in logicalNodes)
{
if (fishingLineNode.Type == nodeType)
{
return fishingLineNode;
}
}
return null;
}
// /// <summary>
// /// 获取指定顺序索引的逻辑节点。
// /// 索引基于 logicalNodes 配置顺序;超出范围或节点为空时返回 null。
// /// </summary>
// public FishingLineNode GetLogicalNode(int logicalIndex)
// {
// if (logicalNodes == null || logicalIndex < 0 || logicalIndex >= logicalNodes.Length)
// {
// return null;
// }
//
// return logicalNodes[logicalIndex];
// }
//
// /// <summary>
// /// 尝试获取指定顺序索引的逻辑节点。
// /// 获取失败时返回 false并将 node 置为 null。
// /// </summary>
// public bool TryGetLogicalNode(int logicalIndex, out FishingLineNode node)
// {
// node = GetLogicalNode(logicalIndex);
// return node != null;
// }
/// <summary>
/// 获取当前起点逻辑节点。
/// 会返回配置顺序中第一个非空节点。
/// </summary>
public FishingLineNode GetStartNode()
{
return FindFirstValidLogicalNode();
}
/// <summary>
/// 获取当前终点逻辑节点。
/// 会返回配置顺序中最后一个非空节点。
/// </summary>
public FishingLineNode GetEndNode()
{
return FindLastValidLogicalNode();
}
/// <summary>
/// 获取当前运行时链路中相邻采样点的实际距离。
/// 这里的采样点包含逻辑节点和虚拟节点。
/// </summary>
public float GetActualDistance(int segmentIndex)
{
if (segmentIndex < 0 || segmentIndex >= chainPoints.Count - 1)
{
return 0f;
}
return Vector3.Distance(chainPoints[segmentIndex].Position, chainPoints[segmentIndex + 1].Position);
}
/// <summary>
/// 返回当前鱼线运行时调试摘要,包含链路结构、长度和极限状态信息。
/// </summary>
public string GetRuntimeDebugSummary()
{
var builder = new StringBuilder(512);
builder.Append("chain:")
.Append(chainDirty ? "dirty" : "ready")
.Append(" logical:")
.Append(LogicalNodeCount)
.Append(" runtimeVirtual:")
.Append(RuntimeVirtualNodeCount)
.Append(" ordered:")
.Append(OrderedNodeCount)
.Append(" total:")
.Append(TotalLineLength.ToString("F2"))
.Append("m")
.Append(" chain:")
.Append(CurrentLogicalChainLength.ToString("F2"))
.Append("m")
.Append(" tension:")
.Append(CurrentTensionRatio.ToString("F3"))
.Append(" breakPercent:")
.Append(CurrentBreakStretchPercent.ToString("F1"))
.Append('%')
.Append(" limit:")
.Append(IsAtLimit ? "yes" : "no")
.Append(" limitTime:")
.Append(LimitStateTime.ToString("F2"))
.Append("s");
for (var i = 0; i < chainPoints.Count; i++)
{
var point = chainPoints[i];
builder.AppendLine()
.Append('#')
.Append(i)
.Append(' ')
.Append(point.GetDebugName())
.Append(" pos:")
.Append(point.Position);
if (point.IsLogical && point.LogicalNode != null)
{
builder.Append(" body:")
.Append(point.LogicalNode.Body != null ? "yes" : "no");
}
if (i < restLengths.Count)
{
builder.Append(" seg rest:")
.Append(restLengths[i].ToString("F3"))
.Append(" actual:")
.Append(GetActualDistance(i).ToString("F3"));
}
}
return builder.ToString();
}
private void ConfigureStartNode()
{
if (logicalNodes == null || logicalNodes.Length == 0 || logicalNodes[0] == null)
{
return;
}
var startNode = logicalNodes[0];
startNode.Type = FishingLineNode.NodeType.Start;
if (startNode.Body != null)
{
startNode.Body.isKinematic = true;
startNode.Body.interpolation = RigidbodyInterpolation.Interpolate;
startNode.Body.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
}
UpdateAnchorNode();
}
private void ConfigureLogicalJoints()
{
runtimeJoints.Clear();
if (logicalNodes == null)
{
return;
}
for (var i = 1; i < logicalNodes.Length; i++)
{
var current = logicalNodes[i];
var previous = logicalNodes[i - 1];
if (current == null || previous == null || current.Body == null || previous.Body == null)
{
continue;
}
current.Body.solverIterations = jointSolverIterations;
current.Body.interpolation = RigidbodyInterpolation.Interpolate;
current.Body.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
var joint = current.GetComponent<ConfigurableJoint>();
if (joint == null)
{
joint = current.gameObject.AddComponent<ConfigurableJoint>();
}
joint.autoConfigureConnectedAnchor = true;
joint.connectedBody = previous.Body;
joint.xMotion = ConfigurableJointMotion.Limited;
joint.yMotion = ConfigurableJointMotion.Limited;
joint.zMotion = ConfigurableJointMotion.Limited;
joint.angularXMotion = ConfigurableJointMotion.Free;
joint.angularYMotion = ConfigurableJointMotion.Free;
joint.angularZMotion = ConfigurableJointMotion.Free;
joint.projectionMode = JointProjectionMode.PositionAndRotation;
joint.projectionDistance = jointProjectionDistance;
joint.projectionAngle = jointProjectionAngle;
var limit = joint.linearLimit;
limit.limit = GetSegmentLength(i - 1);
joint.linearLimit = limit;
runtimeJoints.Add(joint);
}
}
private void UpdateAnchorNode()
{
if (anchorTransform == null || logicalNodes == null || logicalNodes.Length == 0 || logicalNodes[0] == null)
{
return;
}
var startNode = logicalNodes[0];
startNode.transform.SetPositionAndRotation(anchorTransform.position, anchorTransform.rotation);
if (startNode.Body != null)
{
if (!startNode.Body.isKinematic)
{
startNode.Body.linearVelocity = Vector3.zero;
startNode.Body.angularVelocity = Vector3.zero;
}
}
}
private void EvaluateLimitState(float deltaTime)
{
CurrentLogicalChainLength = 0f;
CurrentEndpointDistance = 0f;
CurrentStretchLength = 0f;
MaxSegmentStretchLength = 0f;
MaxOverstretchedSegmentIndex = -1;
CurrentTensionRatio = 0f;
if (logicalNodes == null || logicalNodes.Length < 2)
{
SetLimitState(false);
UpdateBreakCountdown(deltaTime);
return;
}
FishingLineNode firstNode = null;
FishingLineNode lastNode = null;
for (var segmentIndex = 0; segmentIndex < logicalNodes.Length; segmentIndex++)
{
var node = logicalNodes[segmentIndex];
if (node == null)
{
continue;
}
firstNode ??= node;
lastNode = node;
if (segmentIndex >= logicalNodes.Length - 1)
{
continue;
}
var nextNode = logicalNodes[segmentIndex + 1];
if (nextNode == null)
{
continue;
}
var actualDistance = Vector3.Distance(node.Position, nextNode.Position);
var configuredLength = GetSegmentLength(segmentIndex);
CurrentLogicalChainLength += actualDistance;
if (configuredLength > 0.0001f)
{
CurrentTensionRatio = Mathf.Max(CurrentTensionRatio, actualDistance / configuredLength);
}
var segmentStretchLength = Mathf.Max(0f, actualDistance - configuredLength);
if (segmentStretchLength > MaxSegmentStretchLength)
{
MaxSegmentStretchLength = segmentStretchLength;
MaxOverstretchedSegmentIndex = segmentStretchLength > 0f ? segmentIndex : -1;
}
}
if (firstNode != null && lastNode != null && !ReferenceEquals(firstNode, lastNode))
{
CurrentEndpointDistance = Vector3.Distance(firstNode.Position, lastNode.Position);
}
if (TotalLineLength > 0.0001f)
{
CurrentStretchLength = Mathf.Max(0f, CurrentLogicalChainLength - TotalLineLength);
CurrentTensionRatio = Mathf.Max(CurrentTensionRatio, CurrentLogicalChainLength / TotalLineLength);
}
else if (CurrentLogicalChainLength > 0f)
{
CurrentStretchLength = CurrentLogicalChainLength;
CurrentTensionRatio = Mathf.Max(CurrentTensionRatio, 1f);
}
var exceedsTotalLength = CurrentStretchLength > lengthLimitTolerance;
var exceedsSegmentLength = MaxSegmentStretchLength > lengthLimitTolerance;
SetLimitState(exceedsTotalLength || exceedsSegmentLength);
UpdateBreakCountdown(deltaTime);
}
private void SetLimitState(bool isAtLimit)
{
IsAtLimit = isAtLimit;
}
private void UpdateBreakCountdown(float deltaTime)
{
if (!IsBreakCountdownActive)
{
LimitStateTime = 0f;
HasBreakNotificationSent = false;
return;
}
LimitStateTime += Mathf.Max(0f, deltaTime);
if (HasBreakNotificationSent || LimitStateTime < breakLimitDuration)
{
return;
}
HasBreakNotificationSent = true;
NotifyLineBreakRequested();
}
/// <summary>
/// 发出鱼线达到断线条件的消息。
/// 这里预留给外部订阅,当前不在求解器内部直接执行断线逻辑。
/// </summary>
private void NotifyLineBreakRequested()
{
OnLineBreakRequested?.Invoke(this);
}
private void ResetLimitState()
{
CurrentLogicalChainLength = 0f;
CurrentEndpointDistance = 0f;
CurrentStretchLength = 0f;
MaxSegmentStretchLength = 0f;
MaxOverstretchedSegmentIndex = -1;
CurrentTensionRatio = 0f;
IsAtLimit = false;
LimitStateTime = 0f;
HasBreakNotificationSent = false;
}
private float EvaluateBreakStretchPercent(float stretchLength)
{
if (stretchLength <= lengthLimitTolerance)
{
return 0f;
}
if (stretchLength >= breakStretchThreshold)
{
return 100f;
}
if (breakStretchThreshold <= lengthLimitTolerance)
{
return 100f;
}
return Mathf.InverseLerp(lengthLimitTolerance, breakStretchThreshold, stretchLength) * 100f;
}
private void RebuildRuntimeChain()
{
chainPoints.Clear();
restLengths.Clear();
pinnedIndices.Clear();
TotalLineLength = 0f;
runtimeVirtualPointCount = 0;
if (logicalNodes == null || logicalNodes.Length < 2)
{
chainDirty = false;
return;
}
var segmentLayouts = new SegmentLayout[logicalNodes.Length - 1];
for (var segmentIndex = 0; segmentIndex < segmentLayouts.Length; segmentIndex++)
{
segmentLayouts[segmentIndex] = BuildSegmentLayout(segmentIndex);
}
AddLogicalPoint(logicalNodes[0], 0);
pinnedIndices.Add(0);
for (var segmentIndex = 0; segmentIndex < segmentLayouts.Length; segmentIndex++)
{
var fromNode = logicalNodes[segmentIndex];
var toNode = logicalNodes[segmentIndex + 1];
if (fromNode == null || toNode == null)
{
continue;
}
var layout = segmentLayouts[segmentIndex];
AddVirtualPoints(fromNode.Position, toNode.Position, layout, segmentIndex);
for (var gapIndex = 0; gapIndex < layout.GapLengths.Length; gapIndex++)
{
restLengths.Add(layout.GapLengths[gapIndex]);
TotalLineLength += layout.GapLengths[gapIndex];
}
AddLogicalPoint(toNode, segmentIndex + 1);
pinnedIndices.Add(chainPoints.Count - 1);
}
chainDirty = false;
}
private SegmentLayout BuildSegmentLayout(int segmentIndex)
{
var totalLength = GetSegmentLength(segmentIndex);
if (segmentIndex == 0)
{
return new SegmentLayout
{
GapLengths = BuildFirstSegmentGaps(totalLength, firstSegmentStep),
};
}
var sourceNode = logicalNodes[segmentIndex];
var virtualCount = sourceNode != null ? sourceNode.FixedVirtualNodesToNext : 0;
var gapCount = Mathf.Max(1, virtualCount + 1);
var gapLength = totalLength / gapCount;
var gaps = new float[gapCount];
for (var i = 0; i < gaps.Length; i++)
{
gaps[i] = gapLength;
}
return new SegmentLayout
{
GapLengths = gaps,
};
}
private void AddLogicalPoint(FishingLineNode logicalNode, int logicalIndex)
{
if (logicalNode == null)
{
return;
}
chainPoints.Add(new ChainPoint
{
Key = BuildLogicalPointKey(logicalIndex),
Position = logicalNode.Position,
IsLogical = true,
LogicalNode = logicalNode,
SegmentIndex = logicalIndex,
StableIndex = logicalIndex,
});
}
private void AddVirtualPoints(Vector3 fromPosition, Vector3 toPosition, SegmentLayout layout, int segmentIndex)
{
if (layout.VirtualNodeCount == 0)
{
return;
}
var direction = toPosition - fromPosition;
var distance = direction.magnitude;
var normalizedDirection = distance > 0.0001f ? direction / distance : Vector3.down;
var accumulatedDistance = 0f;
for (var virtualIndex = 0; virtualIndex < layout.VirtualNodeCount; virtualIndex++)
{
accumulatedDistance += layout.GapLengths[virtualIndex];
var stableIndex = segmentIndex == 0
? layout.VirtualNodeCount - 1 - virtualIndex
: virtualIndex;
chainPoints.Add(new ChainPoint
{
Key = BuildVirtualPointKey(segmentIndex, stableIndex),
Position = fromPosition + normalizedDirection * accumulatedDistance,
IsLogical = false,
LogicalNode = null,
SegmentIndex = segmentIndex,
StableIndex = stableIndex,
});
runtimeVirtualPointCount++;
}
}
private void SyncLogicalPointPositions()
{
for (var i = 0; i < chainPoints.Count; i++)
{
var point = chainPoints[i];
if (!point.IsLogical || point.LogicalNode == null)
{
continue;
}
point.Position = point.LogicalNode.Position;
}
}
private static float[] BuildFirstSegmentGaps(float totalLength, float step)
{
if (totalLength <= 0f)
{
return new[] { 0f };
}
if (totalLength < step)
{
return new[] { totalLength };
}
var fullStepCount = Mathf.FloorToInt(totalLength / step);
var remainder = totalLength - (fullStepCount * step);
if (remainder > 0.0001f)
{
var gaps = new float[fullStepCount + 1];
gaps[0] = remainder;
for (var i = 1; i < gaps.Length; i++)
{
gaps[i] = step;
}
return gaps;
}
var divisibleGaps = new float[fullStepCount];
for (var i = 0; i < divisibleGaps.Length; i++)
{
divisibleGaps[i] = step;
}
return divisibleGaps;
}
private void UpdateJointLimit(int logicalNodeIndex, float limitValue)
{
if (logicalNodeIndex <= 0 || logicalNodeIndex >= logicalNodes.Length)
{
return;
}
var node = logicalNodes[logicalNodeIndex];
if (node == null)
{
return;
}
var joint = node.GetComponent<ConfigurableJoint>();
if (joint == null)
{
return;
}
var limit = joint.linearLimit;
limit.limit = limitValue;
joint.linearLimit = limit;
}
private float GetSegmentLength(int segmentIndex)
{
if (segmentIndex <= 0)
{
return firstSegmentLength;
}
if (logicalNodes == null || segmentIndex >= logicalNodes.Length)
{
return 0f;
}
var sourceNode = logicalNodes[segmentIndex];
return sourceNode != null ? sourceNode.SegmentLengthToNext : 0f;
}
private FishingLineNode FindFirstValidLogicalNode()
{
if (logicalNodes == null)
{
return null;
}
for (var i = 0; i < logicalNodes.Length; i++)
{
if (logicalNodes[i] != null)
{
return logicalNodes[i];
}
}
return null;
}
private FishingLineNode FindLastValidLogicalNode()
{
if (logicalNodes == null)
{
return null;
}
for (var i = logicalNodes.Length - 1; i >= 0; i--)
{
if (logicalNodes[i] != null)
{
return logicalNodes[i];
}
}
return null;
}
private static long BuildLogicalPointKey(int logicalIndex)
{
return (1L << 62) | (uint)logicalIndex;
}
private static long BuildVirtualPointKey(int segmentIndex, int stableIndex)
{
return ((long)(segmentIndex + 1) << 32) | (uint)stableIndex;
}
}
}