提交测试代码
This commit is contained in:
@@ -22,10 +22,16 @@ namespace F2RopeLine2.FishingLine
|
|||||||
[Min(0.001f)]
|
[Min(0.001f)]
|
||||||
[SerializeField] private float maxDeltaTime = 0.0333333f;
|
[SerializeField] private float maxDeltaTime = 0.0333333f;
|
||||||
|
|
||||||
[Header("Pin Follow")]
|
[Header("Water Surface")]
|
||||||
|
[SerializeField] private bool constrainToWaterSurface = true;
|
||||||
|
[SerializeField] private Transform waterSurfaceTransform;
|
||||||
|
[SerializeField] private float waterSurfaceHeight;
|
||||||
|
[Min(0)]
|
||||||
|
[SerializeField] private int ignoreHeadNodeCount = 1;
|
||||||
|
[Min(0)]
|
||||||
|
[SerializeField] private int ignoreTailNodeCount = 1;
|
||||||
[Min(0f)]
|
[Min(0f)]
|
||||||
[SerializeField] private float pinFollowStrength = 50f;
|
[SerializeField] private float waterSurfaceFollowSpeed = 12f;
|
||||||
[SerializeField] private bool writeBackVirtualNodeTransforms = true;
|
|
||||||
|
|
||||||
[Header("Stability")]
|
[Header("Stability")]
|
||||||
[Min(1)]
|
[Min(1)]
|
||||||
@@ -40,15 +46,15 @@ namespace F2RopeLine2.FishingLine
|
|||||||
[SerializeField] private float wakeDistanceThreshold = 0.001f;
|
[SerializeField] private float wakeDistanceThreshold = 0.001f;
|
||||||
|
|
||||||
[Header("Debug")]
|
[Header("Debug")]
|
||||||
[SerializeField] private bool drawDebugSamples = false;
|
[SerializeField] private bool drawDebugSamples;
|
||||||
[SerializeField] private Color debugSampleColor = new(1f, 0.2f, 0.2f, 1f);
|
[SerializeField] private Color debugSampleColor = new(1f, 0.2f, 0.2f, 1f);
|
||||||
[Min(0.001f)]
|
[Min(0.001f)]
|
||||||
[SerializeField] private float debugSampleRadius = 0.015f;
|
[SerializeField] private float debugSampleRadius = 0.015f;
|
||||||
|
|
||||||
private readonly List<Vector3> positions = new();
|
private readonly List<Vector3> positions = new();
|
||||||
private readonly List<Vector3> previousPositions = new();
|
private readonly List<Vector3> previousPositions = new();
|
||||||
private readonly List<FishingLineNode> sampledNodes = new();
|
private readonly List<long> sampledPointKeys = new();
|
||||||
private readonly List<Vector3> lastPinnedNodePositions = new();
|
private readonly List<Vector3> lastPinnedPointPositions = new();
|
||||||
private readonly List<float> lastRestLengths = new();
|
private readonly List<float> lastRestLengths = new();
|
||||||
private bool[] pinnedFlags = System.Array.Empty<bool>();
|
private bool[] pinnedFlags = System.Array.Empty<bool>();
|
||||||
private float accumulatedTime;
|
private float accumulatedTime;
|
||||||
@@ -96,70 +102,72 @@ namespace F2RopeLine2.FishingLine
|
|||||||
}
|
}
|
||||||
|
|
||||||
solver = sourceSolver;
|
solver = sourceSolver;
|
||||||
var nodes = solver.OrderedNodes;
|
var points = solver.ChainPoints;
|
||||||
var restLengths = solver.RestLengths;
|
var restLengths = solver.RestLengths;
|
||||||
var pinnedIndices = solver.PinnedIndices;
|
var pinnedIndices = solver.PinnedIndices;
|
||||||
if (nodes.Count == 0)
|
if (points.Count == 0)
|
||||||
{
|
{
|
||||||
lineRenderer.positionCount = 0;
|
lineRenderer.positionCount = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var topologyChanged = EnsureBuffers(nodes, pinnedIndices);
|
var topologyChanged = EnsureBuffers(points, pinnedIndices);
|
||||||
if (topologyChanged)
|
if (topologyChanged || ShouldWake(points, restLengths))
|
||||||
{
|
{
|
||||||
WakeUp();
|
WakeUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ShouldWake(nodes, restLengths))
|
Simulate(points, restLengths, deltaTime);
|
||||||
{
|
ApplyToRenderer();
|
||||||
WakeUp();
|
CacheFrameState(points, restLengths);
|
||||||
}
|
|
||||||
|
|
||||||
Simulate(nodes, restLengths, deltaTime);
|
|
||||||
ApplyToRenderer(nodes);
|
|
||||||
CacheFrameState(nodes, restLengths);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool EnsureBuffers(
|
private bool EnsureBuffers(
|
||||||
IReadOnlyList<FishingLineNode> nodes,
|
IReadOnlyList<FishingLineSolver.ChainPoint> points,
|
||||||
IReadOnlyList<int> pinnedIndices)
|
IReadOnlyList<int> pinnedIndices)
|
||||||
{
|
{
|
||||||
var topologyChanged = positions.Count != nodes.Count;
|
var topologyChanged = sampledPointKeys.Count != points.Count;
|
||||||
var previousPositionMap = new Dictionary<FishingLineNode, Vector3>(sampledNodes.Count);
|
if (!topologyChanged)
|
||||||
var previousHistoryMap = new Dictionary<FishingLineNode, Vector3>(sampledNodes.Count);
|
|
||||||
for (var i = 0; i < sampledNodes.Count; i++)
|
|
||||||
{
|
{
|
||||||
var sampledNode = sampledNodes[i];
|
for (var i = 0; i < points.Count; i++)
|
||||||
if (sampledNode == null)
|
{
|
||||||
|
if (sampledPointKeys[i] == points[i].Key)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
previousPositionMap[sampledNode] = positions[i];
|
topologyChanged = true;
|
||||||
previousHistoryMap[sampledNode] = previousPositions[i];
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousPositionMap = new Dictionary<long, Vector3>(sampledPointKeys.Count);
|
||||||
|
var previousHistoryMap = new Dictionary<long, Vector3>(sampledPointKeys.Count);
|
||||||
|
for (var i = 0; i < sampledPointKeys.Count; i++)
|
||||||
|
{
|
||||||
|
previousPositionMap[sampledPointKeys[i]] = positions[i];
|
||||||
|
previousHistoryMap[sampledPointKeys[i]] = previousPositions[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
positions.Clear();
|
positions.Clear();
|
||||||
previousPositions.Clear();
|
previousPositions.Clear();
|
||||||
sampledNodes.Clear();
|
sampledPointKeys.Clear();
|
||||||
pinnedFlags = new bool[nodes.Count];
|
pinnedFlags = new bool[points.Count];
|
||||||
|
|
||||||
for (var i = 0; i < nodes.Count; i++)
|
for (var i = 0; i < points.Count; i++)
|
||||||
{
|
{
|
||||||
var node = nodes[i];
|
var point = points[i];
|
||||||
sampledNodes.Add(node);
|
sampledPointKeys.Add(point.Key);
|
||||||
|
|
||||||
if (node != null && previousPositionMap.TryGetValue(node, out var preservedPosition))
|
if (previousPositionMap.TryGetValue(point.Key, out var preservedPosition))
|
||||||
{
|
{
|
||||||
positions.Add(preservedPosition);
|
positions.Add(preservedPosition);
|
||||||
previousPositions.Add(previousHistoryMap[node]);
|
previousPositions.Add(previousHistoryMap[point.Key]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var position = node != null ? node.Position : Vector3.zero;
|
positions.Add(point.Position);
|
||||||
positions.Add(position);
|
previousPositions.Add(point.Position);
|
||||||
previousPositions.Add(position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < pinnedIndices.Count; i++)
|
for (var i = 0; i < pinnedIndices.Count; i++)
|
||||||
@@ -174,11 +182,14 @@ namespace F2RopeLine2.FishingLine
|
|||||||
return topologyChanged;
|
return topologyChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Simulate(IReadOnlyList<FishingLineNode> nodes, IReadOnlyList<float> restLengths, float deltaTime)
|
private void Simulate(
|
||||||
|
IReadOnlyList<FishingLineSolver.ChainPoint> points,
|
||||||
|
IReadOnlyList<float> restLengths,
|
||||||
|
float deltaTime)
|
||||||
{
|
{
|
||||||
if (isSleeping)
|
if (isSleeping)
|
||||||
{
|
{
|
||||||
PinLogicalNodes(nodes, simulationStep);
|
PinLogicalPoints(points);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,31 +199,33 @@ namespace F2RopeLine2.FishingLine
|
|||||||
var subStepCount = 0;
|
var subStepCount = 0;
|
||||||
while (accumulatedTime >= simulationStep && subStepCount < maxSubStepsPerFrame)
|
while (accumulatedTime >= simulationStep && subStepCount < maxSubStepsPerFrame)
|
||||||
{
|
{
|
||||||
SimulateStep(nodes, restLengths, simulationStep);
|
SimulateStep(points, restLengths, simulationStep);
|
||||||
accumulatedTime -= simulationStep;
|
accumulatedTime -= simulationStep;
|
||||||
subStepCount++;
|
subStepCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subStepCount == 0)
|
if (subStepCount == 0)
|
||||||
{
|
{
|
||||||
PinLogicalNodes(nodes, simulationStep);
|
PinLogicalPoints(points);
|
||||||
ApplySleep(nodes);
|
ApplySleep();
|
||||||
}
|
}
|
||||||
|
|
||||||
EvaluateSleepState(nodes, restLengths);
|
EvaluateSleepState(restLengths);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SimulateStep(IReadOnlyList<FishingLineNode> nodes, IReadOnlyList<float> restLengths, float stepDelta)
|
private void SimulateStep(
|
||||||
|
IReadOnlyList<FishingLineSolver.ChainPoint> points,
|
||||||
|
IReadOnlyList<float> restLengths,
|
||||||
|
float stepDelta)
|
||||||
{
|
{
|
||||||
var gravity = Physics.gravity * gravityScale * stepDelta * stepDelta;
|
var gravity = Physics.gravity * gravityScale * stepDelta * stepDelta;
|
||||||
|
|
||||||
for (var i = 0; i < nodes.Count; i++)
|
for (var i = 0; i < points.Count; i++)
|
||||||
{
|
{
|
||||||
if (pinnedFlags[i])
|
if (pinnedFlags[i])
|
||||||
{
|
{
|
||||||
var pinnedPosition = nodes[i].Position;
|
positions[i] = points[i].Position;
|
||||||
positions[i] = pinnedPosition;
|
previousPositions[i] = points[i].Position;
|
||||||
previousPositions[i] = pinnedPosition;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +237,7 @@ namespace F2RopeLine2.FishingLine
|
|||||||
|
|
||||||
for (var iteration = 0; iteration < solverIterations; iteration++)
|
for (var iteration = 0; iteration < solverIterations; iteration++)
|
||||||
{
|
{
|
||||||
PinLogicalNodes(nodes, stepDelta);
|
PinLogicalPoints(points);
|
||||||
|
|
||||||
for (var segmentIndex = 0; segmentIndex < restLengths.Count; segmentIndex++)
|
for (var segmentIndex = 0; segmentIndex < restLengths.Count; segmentIndex++)
|
||||||
{
|
{
|
||||||
@@ -232,24 +245,22 @@ namespace F2RopeLine2.FishingLine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PinLogicalNodes(nodes, stepDelta);
|
ApplyWaterSurfaceConstraint(stepDelta);
|
||||||
ApplySleep(nodes);
|
PinLogicalPoints(points);
|
||||||
|
ApplySleep();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PinLogicalNodes(IReadOnlyList<FishingLineNode> nodes, float deltaTime)
|
private void PinLogicalPoints(IReadOnlyList<FishingLineSolver.ChainPoint> points)
|
||||||
{
|
{
|
||||||
var followWeight = Mathf.Clamp01(pinFollowStrength * deltaTime);
|
for (var i = 0; i < points.Count; i++)
|
||||||
for (var i = 0; i < nodes.Count; i++)
|
|
||||||
{
|
{
|
||||||
if (!pinnedFlags[i])
|
if (!pinnedFlags[i])
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetPosition = nodes[i].Position;
|
positions[i] = points[i].Position;
|
||||||
positions[i] = Vector3.Lerp(positions[i], targetPosition, followWeight);
|
previousPositions[i] = points[i].Position;
|
||||||
previousPositions[i] = targetPosition;
|
|
||||||
positions[i] = targetPosition;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,9 +306,19 @@ namespace F2RopeLine2.FishingLine
|
|||||||
positions[segmentIndex + 1] -= correction;
|
positions[segmentIndex + 1] -= correction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplySleep(IReadOnlyList<FishingLineNode> nodes)
|
private void ApplyWaterSurfaceConstraint(float stepDelta)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < nodes.Count; i++)
|
if (!constrainToWaterSurface || positions.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var surfaceHeight = waterSurfaceTransform != null ? waterSurfaceTransform.position.y : waterSurfaceHeight;
|
||||||
|
var startIndex = Mathf.Clamp(ignoreHeadNodeCount, 0, positions.Count);
|
||||||
|
var endExclusive = Mathf.Clamp(positions.Count - ignoreTailNodeCount, startIndex, positions.Count);
|
||||||
|
var followFactor = Mathf.Clamp01(waterSurfaceFollowSpeed * stepDelta);
|
||||||
|
|
||||||
|
for (var i = startIndex; i < endExclusive; i++)
|
||||||
{
|
{
|
||||||
if (pinnedFlags[i])
|
if (pinnedFlags[i])
|
||||||
{
|
{
|
||||||
@@ -305,21 +326,25 @@ namespace F2RopeLine2.FishingLine
|
|||||||
}
|
}
|
||||||
|
|
||||||
var current = positions[i];
|
var current = positions[i];
|
||||||
|
if (current.y >= surfaceHeight)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextY = Mathf.Lerp(current.y, surfaceHeight, followFactor);
|
||||||
|
positions[i] = new Vector3(current.x, nextY, current.z);
|
||||||
|
|
||||||
var previous = previousPositions[i];
|
var previous = previousPositions[i];
|
||||||
var velocityMagnitude = (current - previous).magnitude;
|
previousPositions[i] = new Vector3(
|
||||||
var authoredDistance = Vector3.Distance(current, nodes[i].Position);
|
previous.x,
|
||||||
|
Mathf.Lerp(previous.y, nextY, followFactor),
|
||||||
if (velocityMagnitude <= sleepVelocityThreshold && authoredDistance <= sleepDistanceThreshold)
|
previous.z);
|
||||||
{
|
|
||||||
previousPositions[i] = current;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EvaluateSleepState(IReadOnlyList<FishingLineNode> nodes, IReadOnlyList<float> restLengths)
|
private void ApplySleep()
|
||||||
{
|
{
|
||||||
var isStable = true;
|
for (var i = 0; i < positions.Count; i++)
|
||||||
for (var i = 0; i < nodes.Count; i++)
|
|
||||||
{
|
{
|
||||||
if (pinnedFlags[i])
|
if (pinnedFlags[i])
|
||||||
{
|
{
|
||||||
@@ -327,7 +352,24 @@ namespace F2RopeLine2.FishingLine
|
|||||||
}
|
}
|
||||||
|
|
||||||
var velocityMagnitude = (positions[i] - previousPositions[i]).magnitude;
|
var velocityMagnitude = (positions[i] - previousPositions[i]).magnitude;
|
||||||
if (velocityMagnitude > sleepVelocityThreshold)
|
if (velocityMagnitude <= sleepVelocityThreshold)
|
||||||
|
{
|
||||||
|
previousPositions[i] = positions[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EvaluateSleepState(IReadOnlyList<float> restLengths)
|
||||||
|
{
|
||||||
|
var isStable = true;
|
||||||
|
for (var i = 0; i < positions.Count; i++)
|
||||||
|
{
|
||||||
|
if (pinnedFlags[i])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((positions[i] - previousPositions[i]).magnitude > sleepVelocityThreshold)
|
||||||
{
|
{
|
||||||
isStable = false;
|
isStable = false;
|
||||||
break;
|
break;
|
||||||
@@ -367,26 +409,28 @@ namespace F2RopeLine2.FishingLine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldWake(IReadOnlyList<FishingLineNode> nodes, IReadOnlyList<float> restLengths)
|
private bool ShouldWake(
|
||||||
|
IReadOnlyList<FishingLineSolver.ChainPoint> points,
|
||||||
|
IReadOnlyList<float> restLengths)
|
||||||
{
|
{
|
||||||
if (!isSleeping)
|
if (!isSleeping)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastPinnedNodePositions.Count != nodes.Count || lastRestLengths.Count != restLengths.Count)
|
if (lastPinnedPointPositions.Count != points.Count || lastRestLengths.Count != restLengths.Count)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < nodes.Count; i++)
|
for (var i = 0; i < points.Count; i++)
|
||||||
{
|
{
|
||||||
if (!pinnedFlags[i])
|
if (!pinnedFlags[i])
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Vector3.Distance(nodes[i].Position, lastPinnedNodePositions[i]) > wakeDistanceThreshold)
|
if (Vector3.Distance(points[i].Position, lastPinnedPointPositions[i]) > wakeDistanceThreshold)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -403,12 +447,14 @@ namespace F2RopeLine2.FishingLine
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CacheFrameState(IReadOnlyList<FishingLineNode> nodes, IReadOnlyList<float> restLengths)
|
private void CacheFrameState(
|
||||||
|
IReadOnlyList<FishingLineSolver.ChainPoint> points,
|
||||||
|
IReadOnlyList<float> restLengths)
|
||||||
{
|
{
|
||||||
lastPinnedNodePositions.Clear();
|
lastPinnedPointPositions.Clear();
|
||||||
for (var i = 0; i < nodes.Count; i++)
|
for (var i = 0; i < points.Count; i++)
|
||||||
{
|
{
|
||||||
lastPinnedNodePositions.Add(nodes[i] != null ? nodes[i].Position : Vector3.zero);
|
lastPinnedPointPositions.Add(points[i].Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastRestLengths.Clear();
|
lastRestLengths.Clear();
|
||||||
@@ -425,22 +471,12 @@ namespace F2RopeLine2.FishingLine
|
|||||||
accumulatedTime = 0f;
|
accumulatedTime = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyToRenderer(IReadOnlyList<FishingLineNode> nodes)
|
private void ApplyToRenderer()
|
||||||
{
|
{
|
||||||
lineRenderer.positionCount = positions.Count;
|
lineRenderer.positionCount = positions.Count;
|
||||||
for (var i = 0; i < positions.Count; i++)
|
for (var i = 0; i < positions.Count; i++)
|
||||||
{
|
{
|
||||||
lineRenderer.SetPosition(i, positions[i]);
|
lineRenderer.SetPosition(i, positions[i]);
|
||||||
|
|
||||||
if (!writeBackVirtualNodeTransforms)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodes[i].IsRuntimeVirtualNode)
|
|
||||||
{
|
|
||||||
nodes[i].SetVisualPosition(positions[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,30 @@ namespace F2RopeLine2.FishingLine
|
|||||||
{
|
{
|
||||||
public class FishingLineSolver : MonoBehaviour
|
public class FishingLineSolver : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
[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]
|
[Serializable]
|
||||||
private struct SegmentLayout
|
private struct SegmentLayout
|
||||||
{
|
{
|
||||||
public float TotalLength;
|
|
||||||
public float[] GapLengths;
|
public float[] GapLengths;
|
||||||
|
|
||||||
public int VirtualNodeCount => Mathf.Max(0, GapLengths.Length - 1);
|
public int VirtualNodeCount => Mathf.Max(0, GapLengths.Length - 1);
|
||||||
@@ -43,23 +63,19 @@ namespace F2RopeLine2.FishingLine
|
|||||||
[SerializeField] private bool showNodeLabels = true;
|
[SerializeField] private bool showNodeLabels = true;
|
||||||
[SerializeField] private bool showSegmentLabels = true;
|
[SerializeField] private bool showSegmentLabels = true;
|
||||||
|
|
||||||
private readonly List<FishingLineNode> orderedNodes = new();
|
private readonly List<ChainPoint> chainPoints = new();
|
||||||
private readonly List<float> restLengths = new();
|
private readonly List<float> restLengths = new();
|
||||||
private readonly List<int> pinnedIndices = new();
|
private readonly List<int> pinnedIndices = new();
|
||||||
private readonly List<FishingLineNode> firstSegmentVirtualPool = new();
|
|
||||||
private readonly List<FishingLineNode> otherSegmentVirtualPool = new();
|
|
||||||
private readonly List<ConfigurableJoint> runtimeJoints = new();
|
private readonly List<ConfigurableJoint> runtimeJoints = new();
|
||||||
|
|
||||||
private Transform virtualNodeRoot;
|
|
||||||
private bool chainDirty = true;
|
private bool chainDirty = true;
|
||||||
|
private int runtimeVirtualPointCount;
|
||||||
public Transform AnchorTransform => anchorTransform;
|
|
||||||
|
|
||||||
public float FirstSegmentLength => firstSegmentLength;
|
public float FirstSegmentLength => firstSegmentLength;
|
||||||
|
|
||||||
public float FirstSegmentStep => firstSegmentStep;
|
public float FirstSegmentStep => firstSegmentStep;
|
||||||
|
|
||||||
public IReadOnlyList<FishingLineNode> OrderedNodes => orderedNodes;
|
public IReadOnlyList<ChainPoint> ChainPoints => chainPoints;
|
||||||
|
|
||||||
public IReadOnlyList<float> RestLengths => restLengths;
|
public IReadOnlyList<float> RestLengths => restLengths;
|
||||||
|
|
||||||
@@ -69,34 +85,11 @@ namespace F2RopeLine2.FishingLine
|
|||||||
|
|
||||||
public int LogicalNodeCount => logicalNodes?.Length ?? 0;
|
public int LogicalNodeCount => logicalNodes?.Length ?? 0;
|
||||||
|
|
||||||
public int RuntimeVirtualNodeCount => firstSegmentVirtualPool.Count + otherSegmentVirtualPool.Count;
|
public int RuntimeVirtualNodeCount => runtimeVirtualPointCount;
|
||||||
|
|
||||||
public int ActiveRuntimeVirtualNodeCount
|
public int ActiveRuntimeVirtualNodeCount => runtimeVirtualPointCount;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var activeCount = 0;
|
|
||||||
for (var i = 0; i < firstSegmentVirtualPool.Count; i++)
|
|
||||||
{
|
|
||||||
if (firstSegmentVirtualPool[i] != null && firstSegmentVirtualPool[i].gameObject.activeSelf)
|
|
||||||
{
|
|
||||||
activeCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < otherSegmentVirtualPool.Count; i++)
|
public int OrderedNodeCount => chainPoints.Count;
|
||||||
{
|
|
||||||
if (otherSegmentVirtualPool[i] != null && otherSegmentVirtualPool[i].gameObject.activeSelf)
|
|
||||||
{
|
|
||||||
activeCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return activeCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int OrderedNodeCount => orderedNodes.Count;
|
|
||||||
|
|
||||||
public int SegmentCount => restLengths.Count;
|
public int SegmentCount => restLengths.Count;
|
||||||
|
|
||||||
@@ -110,11 +103,6 @@ namespace F2RopeLine2.FishingLine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Awake()
|
|
||||||
{
|
|
||||||
EnsureVirtualNodeRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
if (autoBuildOnStart)
|
if (autoBuildOnStart)
|
||||||
@@ -145,7 +133,9 @@ namespace F2RopeLine2.FishingLine
|
|||||||
RebuildRuntimeChain();
|
RebuildRuntimeChain();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lineRenderer != null && orderedNodes.Count > 1)
|
SyncLogicalPointPositions();
|
||||||
|
|
||||||
|
if (lineRenderer != null && chainPoints.Count > 1)
|
||||||
{
|
{
|
||||||
lineRenderer.Render(this, Time.deltaTime);
|
lineRenderer.Render(this, Time.deltaTime);
|
||||||
}
|
}
|
||||||
@@ -162,8 +152,6 @@ namespace F2RopeLine2.FishingLine
|
|||||||
[ContextMenu("Build Line")]
|
[ContextMenu("Build Line")]
|
||||||
public void BuildLine()
|
public void BuildLine()
|
||||||
{
|
{
|
||||||
EnsureVirtualNodeRoot();
|
|
||||||
CleanupDeadReferences();
|
|
||||||
ConfigureStartNode();
|
ConfigureStartNode();
|
||||||
ConfigureLogicalJoints();
|
ConfigureLogicalJoints();
|
||||||
RebuildRuntimeChain();
|
RebuildRuntimeChain();
|
||||||
@@ -187,33 +175,14 @@ namespace F2RopeLine2.FishingLine
|
|||||||
BuildLine();
|
BuildLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetOrderedNode(int index, out FishingLineNode node)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= orderedNodes.Count)
|
|
||||||
{
|
|
||||||
node = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
node = orderedNodes[index];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float GetActualDistance(int segmentIndex)
|
public float GetActualDistance(int segmentIndex)
|
||||||
{
|
{
|
||||||
if (segmentIndex < 0 || segmentIndex >= orderedNodes.Count - 1)
|
if (segmentIndex < 0 || segmentIndex >= chainPoints.Count - 1)
|
||||||
{
|
{
|
||||||
return 0f;
|
return 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fromNode = orderedNodes[segmentIndex];
|
return Vector3.Distance(chainPoints[segmentIndex].Position, chainPoints[segmentIndex + 1].Position);
|
||||||
var toNode = orderedNodes[segmentIndex + 1];
|
|
||||||
if (fromNode == null || toNode == null)
|
|
||||||
{
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Vector3.Distance(fromNode.Position, toNode.Position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetRuntimeDebugSummary()
|
public string GetRuntimeDebugSummary()
|
||||||
@@ -224,8 +193,6 @@ namespace F2RopeLine2.FishingLine
|
|||||||
.Append(" logical:")
|
.Append(" logical:")
|
||||||
.Append(LogicalNodeCount)
|
.Append(LogicalNodeCount)
|
||||||
.Append(" runtimeVirtual:")
|
.Append(" runtimeVirtual:")
|
||||||
.Append(ActiveRuntimeVirtualNodeCount)
|
|
||||||
.Append("/")
|
|
||||||
.Append(RuntimeVirtualNodeCount)
|
.Append(RuntimeVirtualNodeCount)
|
||||||
.Append(" ordered:")
|
.Append(" ordered:")
|
||||||
.Append(OrderedNodeCount)
|
.Append(OrderedNodeCount)
|
||||||
@@ -233,19 +200,22 @@ namespace F2RopeLine2.FishingLine
|
|||||||
.Append(TotalLineLength.ToString("F2"))
|
.Append(TotalLineLength.ToString("F2"))
|
||||||
.Append("m");
|
.Append("m");
|
||||||
|
|
||||||
for (var i = 0; i < orderedNodes.Count; i++)
|
for (var i = 0; i < chainPoints.Count; i++)
|
||||||
{
|
{
|
||||||
var node = orderedNodes[i];
|
var point = chainPoints[i];
|
||||||
if (node == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.AppendLine()
|
builder.AppendLine()
|
||||||
.Append('#')
|
.Append('#')
|
||||||
.Append(i)
|
.Append(i)
|
||||||
.Append(' ')
|
.Append(' ')
|
||||||
.Append(node.GetDebugSummary());
|
.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)
|
if (i < restLengths.Count)
|
||||||
{
|
{
|
||||||
@@ -346,74 +316,49 @@ namespace F2RopeLine2.FishingLine
|
|||||||
|
|
||||||
private void RebuildRuntimeChain()
|
private void RebuildRuntimeChain()
|
||||||
{
|
{
|
||||||
CleanupDeadReferences();
|
chainPoints.Clear();
|
||||||
orderedNodes.Clear();
|
|
||||||
restLengths.Clear();
|
restLengths.Clear();
|
||||||
pinnedIndices.Clear();
|
pinnedIndices.Clear();
|
||||||
TotalLineLength = 0f;
|
TotalLineLength = 0f;
|
||||||
|
runtimeVirtualPointCount = 0;
|
||||||
|
|
||||||
if (logicalNodes == null || logicalNodes.Length < 2)
|
if (logicalNodes == null || logicalNodes.Length < 2)
|
||||||
{
|
{
|
||||||
DestroyAllRuntimeVirtualNodes();
|
|
||||||
chainDirty = false;
|
chainDirty = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var segmentLayouts = new SegmentLayout[logicalNodes.Length - 1];
|
var segmentLayouts = new SegmentLayout[logicalNodes.Length - 1];
|
||||||
var firstSegmentVirtualCount = 0;
|
|
||||||
var otherSegmentVirtualCount = 0;
|
|
||||||
for (var segmentIndex = 0; segmentIndex < segmentLayouts.Length; segmentIndex++)
|
for (var segmentIndex = 0; segmentIndex < segmentLayouts.Length; segmentIndex++)
|
||||||
{
|
{
|
||||||
segmentLayouts[segmentIndex] = BuildSegmentLayout(segmentIndex);
|
segmentLayouts[segmentIndex] = BuildSegmentLayout(segmentIndex);
|
||||||
if (segmentIndex == 0)
|
|
||||||
{
|
|
||||||
firstSegmentVirtualCount = segmentLayouts[segmentIndex].VirtualNodeCount;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
otherSegmentVirtualCount += segmentLayouts[segmentIndex].VirtualNodeCount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureFirstSegmentVirtualPool(firstSegmentVirtualCount);
|
AddLogicalPoint(logicalNodes[0], 0);
|
||||||
EnsureOtherSegmentVirtualPool(otherSegmentVirtualCount);
|
|
||||||
|
|
||||||
orderedNodes.Add(logicalNodes[0]);
|
|
||||||
pinnedIndices.Add(0);
|
pinnedIndices.Add(0);
|
||||||
|
|
||||||
var otherVirtualCursor = 0;
|
|
||||||
for (var segmentIndex = 0; segmentIndex < segmentLayouts.Length; segmentIndex++)
|
for (var segmentIndex = 0; segmentIndex < segmentLayouts.Length; segmentIndex++)
|
||||||
{
|
{
|
||||||
var fromNode = logicalNodes[segmentIndex];
|
var fromNode = logicalNodes[segmentIndex];
|
||||||
var toNode = logicalNodes[segmentIndex + 1];
|
var toNode = logicalNodes[segmentIndex + 1];
|
||||||
var layout = segmentLayouts[segmentIndex];
|
if (fromNode == null || toNode == null)
|
||||||
var segmentVirtualNodes = GetSegmentVirtualNodes(segmentIndex, layout.VirtualNodeCount, ref otherVirtualCursor);
|
|
||||||
|
|
||||||
for (var virtualIndex = 0; virtualIndex < segmentVirtualNodes.Count; virtualIndex++)
|
|
||||||
{
|
{
|
||||||
var virtualNode = segmentVirtualNodes[virtualIndex];
|
continue;
|
||||||
virtualNode.SetRuntimeVirtual(true, orderedNodes.Count);
|
|
||||||
virtualNode.name = $"VirtualNode_{segmentIndex}_{virtualIndex}";
|
|
||||||
virtualNode.transform.SetParent(virtualNodeRoot, false);
|
|
||||||
orderedNodes.Add(virtualNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var layout = segmentLayouts[segmentIndex];
|
||||||
|
AddVirtualPoints(fromNode.Position, toNode.Position, layout, segmentIndex);
|
||||||
|
|
||||||
for (var gapIndex = 0; gapIndex < layout.GapLengths.Length; gapIndex++)
|
for (var gapIndex = 0; gapIndex < layout.GapLengths.Length; gapIndex++)
|
||||||
{
|
{
|
||||||
var gapLength = layout.GapLengths[gapIndex];
|
restLengths.Add(layout.GapLengths[gapIndex]);
|
||||||
restLengths.Add(gapLength);
|
TotalLineLength += layout.GapLengths[gapIndex];
|
||||||
TotalLineLength += gapLength;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
orderedNodes.Add(toNode);
|
AddLogicalPoint(toNode, segmentIndex + 1);
|
||||||
pinnedIndices.Add(orderedNodes.Count - 1);
|
pinnedIndices.Add(chainPoints.Count - 1);
|
||||||
|
|
||||||
PlaceVirtualNodesBetween(fromNode, toNode, layout, segmentVirtualNodes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DisableUnusedFirstSegmentVirtualNodes(firstSegmentVirtualCount);
|
|
||||||
DisableUnusedOtherSegmentVirtualNodes(otherSegmentVirtualCount);
|
|
||||||
UpdateJointLimitsFromConfig();
|
|
||||||
chainDirty = false;
|
chainDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,14 +369,14 @@ namespace F2RopeLine2.FishingLine
|
|||||||
{
|
{
|
||||||
return new SegmentLayout
|
return new SegmentLayout
|
||||||
{
|
{
|
||||||
TotalLength = totalLength,
|
|
||||||
GapLengths = BuildFirstSegmentGaps(totalLength, firstSegmentStep),
|
GapLengths = BuildFirstSegmentGaps(totalLength, firstSegmentStep),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var virtualCount = logicalNodes[segmentIndex].FixedVirtualNodesToNext;
|
var sourceNode = logicalNodes[segmentIndex];
|
||||||
|
var virtualCount = sourceNode != null ? sourceNode.FixedVirtualNodesToNext : 0;
|
||||||
var gapCount = Mathf.Max(1, virtualCount + 1);
|
var gapCount = Mathf.Max(1, virtualCount + 1);
|
||||||
var gapLength = gapCount > 0 ? totalLength / gapCount : totalLength;
|
var gapLength = totalLength / gapCount;
|
||||||
var gaps = new float[gapCount];
|
var gaps = new float[gapCount];
|
||||||
for (var i = 0; i < gaps.Length; i++)
|
for (var i = 0; i < gaps.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -440,11 +385,74 @@ namespace F2RopeLine2.FishingLine
|
|||||||
|
|
||||||
return new SegmentLayout
|
return new SegmentLayout
|
||||||
{
|
{
|
||||||
TotalLength = totalLength,
|
|
||||||
GapLengths = gaps,
|
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)
|
private static float[] BuildFirstSegmentGaps(float totalLength, float step)
|
||||||
{
|
{
|
||||||
if (totalLength <= 0f)
|
if (totalLength <= 0f)
|
||||||
@@ -480,167 +488,6 @@ namespace F2RopeLine2.FishingLine
|
|||||||
return divisibleGaps;
|
return divisibleGaps;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlaceVirtualNodesBetween(
|
|
||||||
FishingLineNode fromNode,
|
|
||||||
FishingLineNode toNode,
|
|
||||||
SegmentLayout layout,
|
|
||||||
IReadOnlyList<FishingLineNode> segmentVirtualNodes)
|
|
||||||
{
|
|
||||||
if (layout.VirtualNodeCount == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var direction = toNode.Position - fromNode.Position;
|
|
||||||
var distance = direction.magnitude;
|
|
||||||
var normalizedDirection = distance > 0.0001f ? direction / distance : Vector3.down;
|
|
||||||
|
|
||||||
var accumulatedDistance = 0f;
|
|
||||||
for (var i = 0; i < layout.VirtualNodeCount; i++)
|
|
||||||
{
|
|
||||||
accumulatedDistance += layout.GapLengths[i];
|
|
||||||
var position = fromNode.Position + normalizedDirection * accumulatedDistance;
|
|
||||||
var virtualNode = segmentVirtualNodes[i];
|
|
||||||
var wasActive = virtualNode.gameObject.activeSelf;
|
|
||||||
if (!Application.isPlaying || !wasActive)
|
|
||||||
{
|
|
||||||
virtualNode.SetVisualPosition(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtualNode.gameObject.SetActive(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<FishingLineNode> GetSegmentVirtualNodes(int segmentIndex, int virtualCount, ref int otherVirtualCursor)
|
|
||||||
{
|
|
||||||
var result = new List<FishingLineNode>(virtualCount);
|
|
||||||
if (virtualCount <= 0)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (segmentIndex == 0)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < virtualCount; i++)
|
|
||||||
{
|
|
||||||
result.Add(firstSegmentVirtualPool[virtualCount - 1 - i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < virtualCount; i++)
|
|
||||||
{
|
|
||||||
result.Add(otherSegmentVirtualPool[otherVirtualCursor + i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
otherVirtualCursor += virtualCount;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureFirstSegmentVirtualPool(int targetCount)
|
|
||||||
{
|
|
||||||
EnsureVirtualNodeRoot();
|
|
||||||
|
|
||||||
while (firstSegmentVirtualPool.Count < targetCount)
|
|
||||||
{
|
|
||||||
var node = CreateRuntimeVirtualNode($"FirstSegmentVirtualNode_{firstSegmentVirtualPool.Count}");
|
|
||||||
firstSegmentVirtualPool.Add(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureOtherSegmentVirtualPool(int targetCount)
|
|
||||||
{
|
|
||||||
EnsureVirtualNodeRoot();
|
|
||||||
|
|
||||||
while (otherSegmentVirtualPool.Count < targetCount)
|
|
||||||
{
|
|
||||||
var node = CreateRuntimeVirtualNode($"OtherSegmentVirtualNode_{otherSegmentVirtualPool.Count}");
|
|
||||||
otherSegmentVirtualPool.Add(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private FishingLineNode CreateRuntimeVirtualNode(string nodeName)
|
|
||||||
{
|
|
||||||
var runtimeObject = new GameObject(nodeName);
|
|
||||||
runtimeObject.transform.SetParent(virtualNodeRoot, false);
|
|
||||||
|
|
||||||
var node = runtimeObject.AddComponent<FishingLineNode>();
|
|
||||||
node.SetRuntimeVirtual(true, -1);
|
|
||||||
runtimeObject.hideFlags = HideFlags.DontSave;
|
|
||||||
runtimeObject.SetActive(false);
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisableUnusedFirstSegmentVirtualNodes(int usedCount)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < firstSegmentVirtualPool.Count; i++)
|
|
||||||
{
|
|
||||||
var node = firstSegmentVirtualPool[i];
|
|
||||||
var active = i < usedCount;
|
|
||||||
if (node != null)
|
|
||||||
{
|
|
||||||
node.gameObject.SetActive(active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisableUnusedOtherSegmentVirtualNodes(int usedCount)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < otherSegmentVirtualPool.Count; i++)
|
|
||||||
{
|
|
||||||
var node = otherSegmentVirtualPool[i];
|
|
||||||
var active = i < usedCount;
|
|
||||||
if (node != null)
|
|
||||||
{
|
|
||||||
node.gameObject.SetActive(active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DestroyAllRuntimeVirtualNodes()
|
|
||||||
{
|
|
||||||
for (var i = firstSegmentVirtualPool.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
DestroyNodeObject(firstSegmentVirtualPool[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = otherSegmentVirtualPool.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
DestroyNodeObject(otherSegmentVirtualPool[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
firstSegmentVirtualPool.Clear();
|
|
||||||
otherSegmentVirtualPool.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CleanupDeadReferences()
|
|
||||||
{
|
|
||||||
firstSegmentVirtualPool.RemoveAll(node => node == null);
|
|
||||||
otherSegmentVirtualPool.RemoveAll(node => node == null);
|
|
||||||
runtimeJoints.RemoveAll(joint => joint == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureVirtualNodeRoot()
|
|
||||||
{
|
|
||||||
if (virtualNodeRoot != null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var existing = transform.Find("_RuntimeVirtualNodes");
|
|
||||||
if (existing != null)
|
|
||||||
{
|
|
||||||
virtualNodeRoot = existing;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var root = new GameObject("_RuntimeVirtualNodes");
|
|
||||||
root.transform.SetParent(transform, false);
|
|
||||||
root.hideFlags = HideFlags.DontSave;
|
|
||||||
virtualNodeRoot = root.transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateJointLimitsFromConfig()
|
private void UpdateJointLimitsFromConfig()
|
||||||
{
|
{
|
||||||
for (var i = 1; i < logicalNodes.Length; i++)
|
for (var i = 1; i < logicalNodes.Length; i++)
|
||||||
@@ -689,43 +536,33 @@ namespace F2RopeLine2.FishingLine
|
|||||||
return sourceNode != null ? sourceNode.SegmentLengthToNext : 0f;
|
return sourceNode != null ? sourceNode.SegmentLengthToNext : 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DestroyNodeObject(FishingLineNode node)
|
private static long BuildLogicalPointKey(int logicalIndex)
|
||||||
{
|
{
|
||||||
if (node == null)
|
return (1L << 62) | (uint)logicalIndex;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Application.isPlaying)
|
private static long BuildVirtualPointKey(int segmentIndex, int stableIndex)
|
||||||
{
|
{
|
||||||
Destroy(node.gameObject);
|
return ((long)(segmentIndex + 1) << 32) | (uint)stableIndex;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DestroyImmediate(node.gameObject);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDrawGizmosSelected()
|
private void OnDrawGizmosSelected()
|
||||||
{
|
{
|
||||||
if (!drawDebugChain || orderedNodes.Count < 2 || restLengths.Count == 0)
|
if (!drawDebugChain || chainPoints.Count < 2 || restLengths.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Gizmos.color = debugColor;
|
Gizmos.color = debugColor;
|
||||||
for (var i = 0; i < orderedNodes.Count - 1; i++)
|
for (var i = 0; i < chainPoints.Count - 1; i++)
|
||||||
{
|
{
|
||||||
if (orderedNodes[i] == null || orderedNodes[i + 1] == null)
|
var from = chainPoints[i].Position;
|
||||||
{
|
var to = chainPoints[i + 1].Position;
|
||||||
continue;
|
Gizmos.DrawLine(from, to);
|
||||||
}
|
Gizmos.DrawSphere(from, debugNodeRadius);
|
||||||
|
|
||||||
Gizmos.DrawLine(orderedNodes[i].Position, orderedNodes[i + 1].Position);
|
var midPoint = (from + to) * 0.5f;
|
||||||
Gizmos.DrawSphere(orderedNodes[i].Position, debugNodeRadius);
|
var actualLength = Vector3.Distance(from, to);
|
||||||
|
|
||||||
var midPoint = (orderedNodes[i].Position + orderedNodes[i + 1].Position) * 0.5f;
|
|
||||||
var actualLength = Vector3.Distance(orderedNodes[i].Position, orderedNodes[i + 1].Position);
|
|
||||||
var restLength = i < restLengths.Count ? restLengths[i] : 0f;
|
var restLength = i < restLengths.Count ? restLengths[i] : 0f;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
@@ -739,10 +576,7 @@ namespace F2RopeLine2.FishingLine
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orderedNodes.Count > 0 && orderedNodes[^1] != null)
|
Gizmos.DrawSphere(chainPoints[^1].Position, debugNodeRadius);
|
||||||
{
|
|
||||||
Gizmos.DrawSphere(orderedNodes[^1].Position, debugNodeRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
if (!showNodeLabels)
|
if (!showNodeLabels)
|
||||||
@@ -750,19 +584,14 @@ namespace F2RopeLine2.FishingLine
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < orderedNodes.Count; i++)
|
for (var i = 0; i < chainPoints.Count; i++)
|
||||||
{
|
{
|
||||||
var node = orderedNodes[i];
|
var point = chainPoints[i];
|
||||||
if (node == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pinned = pinnedIndices.Contains(i) ? " Pinned" : string.Empty;
|
var pinned = pinnedIndices.Contains(i) ? " Pinned" : string.Empty;
|
||||||
UnityEditor.Handles.color = debugColor;
|
UnityEditor.Handles.color = debugColor;
|
||||||
UnityEditor.Handles.Label(
|
UnityEditor.Handles.Label(
|
||||||
node.Position + Vector3.up * 0.04f,
|
point.Position + Vector3.up * 0.04f,
|
||||||
$"#{i} {node.GetDebugName()}{pinned}");
|
$"#{i} {point.GetDebugName()}{pinned}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user