提交测试代码
This commit is contained in:
@@ -598,15 +598,10 @@ MonoBehaviour:
|
|||||||
solver: {fileID: 603012225}
|
solver: {fileID: 603012225}
|
||||||
initialFirstSegmentLength: 1.2
|
initialFirstSegmentLength: 1.2
|
||||||
minFirstSegmentLength: 0.1
|
minFirstSegmentLength: 0.1
|
||||||
maxFirstSegmentLength: 5
|
maxFirstSegmentLength: 50
|
||||||
lineAdjustSpeed: 1
|
lineAdjustSpeed: 1
|
||||||
extendKey: 273
|
extendKey: 273
|
||||||
retractKey: 274
|
retractKey: 274
|
||||||
rebuildKey: 114
|
|
||||||
showGui: 1
|
|
||||||
showNodeDetails: 1
|
|
||||||
showSegmentDetails: 1
|
|
||||||
detailScroll: {x: 0, y: 0}
|
|
||||||
--- !u!1 &832575517
|
--- !u!1 &832575517
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -960,7 +955,7 @@ GameObject:
|
|||||||
- component: {fileID: 1939106084}
|
- component: {fileID: 1939106084}
|
||||||
- component: {fileID: 1939106083}
|
- component: {fileID: 1939106083}
|
||||||
- component: {fileID: 1939106082}
|
- component: {fileID: 1939106082}
|
||||||
m_Layer: 0
|
m_Layer: 6
|
||||||
m_Name: Terrain
|
m_Name: Terrain
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
|
|||||||
@@ -17,12 +17,28 @@ namespace F2RopeLine2.FishingLine
|
|||||||
[SerializeField] private float damping = 0.98f;
|
[SerializeField] private float damping = 0.98f;
|
||||||
[Min(0f)]
|
[Min(0f)]
|
||||||
[SerializeField] private float gravityScale = 1f;
|
[SerializeField] private float gravityScale = 1f;
|
||||||
|
[Min(0.001f)]
|
||||||
|
[SerializeField] private float simulationStep = 0.0166667f;
|
||||||
|
[Min(0.001f)]
|
||||||
|
[SerializeField] private float maxDeltaTime = 0.0333333f;
|
||||||
|
|
||||||
[Header("Pin Follow")]
|
[Header("Pin Follow")]
|
||||||
[Min(0f)]
|
[Min(0f)]
|
||||||
[SerializeField] private float pinFollowStrength = 50f;
|
[SerializeField] private float pinFollowStrength = 50f;
|
||||||
[SerializeField] private bool writeBackVirtualNodeTransforms = true;
|
[SerializeField] private bool writeBackVirtualNodeTransforms = true;
|
||||||
|
|
||||||
|
[Header("Stability")]
|
||||||
|
[Min(1)]
|
||||||
|
[SerializeField] private int maxSubStepsPerFrame = 2;
|
||||||
|
[Min(0f)]
|
||||||
|
[SerializeField] private float sleepVelocityThreshold = 0.001f;
|
||||||
|
[Min(0f)]
|
||||||
|
[SerializeField] private float sleepDistanceThreshold = 0.002f;
|
||||||
|
[Min(1)]
|
||||||
|
[SerializeField] private int stableFramesBeforeSleep = 4;
|
||||||
|
[Min(0f)]
|
||||||
|
[SerializeField] private float wakeDistanceThreshold = 0.001f;
|
||||||
|
|
||||||
[Header("Debug")]
|
[Header("Debug")]
|
||||||
[SerializeField] private bool drawDebugSamples = false;
|
[SerializeField] private bool drawDebugSamples = false;
|
||||||
[SerializeField] private Color debugSampleColor = new(1f, 0.2f, 0.2f, 1f);
|
[SerializeField] private Color debugSampleColor = new(1f, 0.2f, 0.2f, 1f);
|
||||||
@@ -31,7 +47,13 @@ namespace F2RopeLine2.FishingLine
|
|||||||
|
|
||||||
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<Vector3> lastPinnedNodePositions = 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 bool isSleeping;
|
||||||
|
private int stableFrameCounter;
|
||||||
|
|
||||||
public int SampleCount => positions.Count;
|
public int SampleCount => positions.Count;
|
||||||
|
|
||||||
@@ -83,27 +105,59 @@ namespace F2RopeLine2.FishingLine
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureBuffers(nodes, pinnedIndices);
|
var topologyChanged = EnsureBuffers(nodes, pinnedIndices);
|
||||||
|
if (topologyChanged)
|
||||||
|
{
|
||||||
|
WakeUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShouldWake(nodes, restLengths))
|
||||||
|
{
|
||||||
|
WakeUp();
|
||||||
|
}
|
||||||
|
|
||||||
Simulate(nodes, restLengths, deltaTime);
|
Simulate(nodes, restLengths, deltaTime);
|
||||||
ApplyToRenderer(nodes);
|
ApplyToRenderer(nodes);
|
||||||
|
CacheFrameState(nodes, restLengths);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureBuffers(
|
private bool EnsureBuffers(
|
||||||
IReadOnlyList<FishingLineNode> nodes,
|
IReadOnlyList<FishingLineNode> nodes,
|
||||||
IReadOnlyList<int> pinnedIndices)
|
IReadOnlyList<int> pinnedIndices)
|
||||||
{
|
{
|
||||||
if (positions.Count == nodes.Count)
|
var topologyChanged = positions.Count != nodes.Count;
|
||||||
|
var previousPositionMap = new Dictionary<FishingLineNode, Vector3>(sampledNodes.Count);
|
||||||
|
var previousHistoryMap = new Dictionary<FishingLineNode, Vector3>(sampledNodes.Count);
|
||||||
|
for (var i = 0; i < sampledNodes.Count; i++)
|
||||||
{
|
{
|
||||||
return;
|
var sampledNode = sampledNodes[i];
|
||||||
|
if (sampledNode == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
previousPositionMap[sampledNode] = positions[i];
|
||||||
|
previousHistoryMap[sampledNode] = previousPositions[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
positions.Clear();
|
positions.Clear();
|
||||||
previousPositions.Clear();
|
previousPositions.Clear();
|
||||||
|
sampledNodes.Clear();
|
||||||
pinnedFlags = new bool[nodes.Count];
|
pinnedFlags = new bool[nodes.Count];
|
||||||
|
|
||||||
for (var i = 0; i < nodes.Count; i++)
|
for (var i = 0; i < nodes.Count; i++)
|
||||||
{
|
{
|
||||||
var position = nodes[i].Position;
|
var node = nodes[i];
|
||||||
|
sampledNodes.Add(node);
|
||||||
|
|
||||||
|
if (node != null && previousPositionMap.TryGetValue(node, out var preservedPosition))
|
||||||
|
{
|
||||||
|
positions.Add(preservedPosition);
|
||||||
|
previousPositions.Add(previousHistoryMap[node]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var position = node != null ? node.Position : Vector3.zero;
|
||||||
positions.Add(position);
|
positions.Add(position);
|
||||||
previousPositions.Add(position);
|
previousPositions.Add(position);
|
||||||
}
|
}
|
||||||
@@ -116,12 +170,41 @@ namespace F2RopeLine2.FishingLine
|
|||||||
pinnedFlags[pinnedIndex] = true;
|
pinnedFlags[pinnedIndex] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return topologyChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Simulate(IReadOnlyList<FishingLineNode> nodes, IReadOnlyList<float> restLengths, float deltaTime)
|
private void Simulate(IReadOnlyList<FishingLineNode> nodes, IReadOnlyList<float> restLengths, float deltaTime)
|
||||||
{
|
{
|
||||||
var simulationDelta = Mathf.Max(0.0001f, deltaTime);
|
if (isSleeping)
|
||||||
var gravity = Physics.gravity * gravityScale * simulationDelta * simulationDelta;
|
{
|
||||||
|
PinLogicalNodes(nodes, simulationStep);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var clampedDelta = Mathf.Clamp(deltaTime, 0f, maxDeltaTime);
|
||||||
|
accumulatedTime = Mathf.Min(accumulatedTime + clampedDelta, simulationStep * maxSubStepsPerFrame);
|
||||||
|
|
||||||
|
var subStepCount = 0;
|
||||||
|
while (accumulatedTime >= simulationStep && subStepCount < maxSubStepsPerFrame)
|
||||||
|
{
|
||||||
|
SimulateStep(nodes, restLengths, simulationStep);
|
||||||
|
accumulatedTime -= simulationStep;
|
||||||
|
subStepCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subStepCount == 0)
|
||||||
|
{
|
||||||
|
PinLogicalNodes(nodes, simulationStep);
|
||||||
|
ApplySleep(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
EvaluateSleepState(nodes, restLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SimulateStep(IReadOnlyList<FishingLineNode> nodes, IReadOnlyList<float> restLengths, float stepDelta)
|
||||||
|
{
|
||||||
|
var gravity = Physics.gravity * gravityScale * stepDelta * stepDelta;
|
||||||
|
|
||||||
for (var i = 0; i < nodes.Count; i++)
|
for (var i = 0; i < nodes.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -141,7 +224,7 @@ namespace F2RopeLine2.FishingLine
|
|||||||
|
|
||||||
for (var iteration = 0; iteration < solverIterations; iteration++)
|
for (var iteration = 0; iteration < solverIterations; iteration++)
|
||||||
{
|
{
|
||||||
PinLogicalNodes(nodes, simulationDelta);
|
PinLogicalNodes(nodes, stepDelta);
|
||||||
|
|
||||||
for (var segmentIndex = 0; segmentIndex < restLengths.Count; segmentIndex++)
|
for (var segmentIndex = 0; segmentIndex < restLengths.Count; segmentIndex++)
|
||||||
{
|
{
|
||||||
@@ -149,7 +232,8 @@ namespace F2RopeLine2.FishingLine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PinLogicalNodes(nodes, simulationDelta);
|
PinLogicalNodes(nodes, stepDelta);
|
||||||
|
ApplySleep(nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PinLogicalNodes(IReadOnlyList<FishingLineNode> nodes, float deltaTime)
|
private void PinLogicalNodes(IReadOnlyList<FishingLineNode> nodes, float deltaTime)
|
||||||
@@ -211,6 +295,136 @@ namespace F2RopeLine2.FishingLine
|
|||||||
positions[segmentIndex + 1] -= correction;
|
positions[segmentIndex + 1] -= correction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplySleep(IReadOnlyList<FishingLineNode> nodes)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < nodes.Count; i++)
|
||||||
|
{
|
||||||
|
if (pinnedFlags[i])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var current = positions[i];
|
||||||
|
var previous = previousPositions[i];
|
||||||
|
var velocityMagnitude = (current - previous).magnitude;
|
||||||
|
var authoredDistance = Vector3.Distance(current, nodes[i].Position);
|
||||||
|
|
||||||
|
if (velocityMagnitude <= sleepVelocityThreshold && authoredDistance <= sleepDistanceThreshold)
|
||||||
|
{
|
||||||
|
previousPositions[i] = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EvaluateSleepState(IReadOnlyList<FishingLineNode> nodes, IReadOnlyList<float> restLengths)
|
||||||
|
{
|
||||||
|
var isStable = true;
|
||||||
|
for (var i = 0; i < nodes.Count; i++)
|
||||||
|
{
|
||||||
|
if (pinnedFlags[i])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var velocityMagnitude = (positions[i] - previousPositions[i]).magnitude;
|
||||||
|
if (velocityMagnitude > sleepVelocityThreshold)
|
||||||
|
{
|
||||||
|
isStable = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isStable)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < restLengths.Count; i++)
|
||||||
|
{
|
||||||
|
var error = Mathf.Abs(Vector3.Distance(positions[i], positions[i + 1]) - restLengths[i]);
|
||||||
|
if (error > sleepDistanceThreshold)
|
||||||
|
{
|
||||||
|
isStable = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isStable)
|
||||||
|
{
|
||||||
|
stableFrameCounter = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stableFrameCounter++;
|
||||||
|
if (stableFrameCounter < stableFramesBeforeSleep)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSleeping = true;
|
||||||
|
accumulatedTime = 0f;
|
||||||
|
for (var i = 0; i < positions.Count; i++)
|
||||||
|
{
|
||||||
|
previousPositions[i] = positions[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldWake(IReadOnlyList<FishingLineNode> nodes, IReadOnlyList<float> restLengths)
|
||||||
|
{
|
||||||
|
if (!isSleeping)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPinnedNodePositions.Count != nodes.Count || lastRestLengths.Count != restLengths.Count)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < nodes.Count; i++)
|
||||||
|
{
|
||||||
|
if (!pinnedFlags[i])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Vector3.Distance(nodes[i].Position, lastPinnedNodePositions[i]) > wakeDistanceThreshold)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < restLengths.Count; i++)
|
||||||
|
{
|
||||||
|
if (Mathf.Abs(restLengths[i] - lastRestLengths[i]) > wakeDistanceThreshold)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CacheFrameState(IReadOnlyList<FishingLineNode> nodes, IReadOnlyList<float> restLengths)
|
||||||
|
{
|
||||||
|
lastPinnedNodePositions.Clear();
|
||||||
|
for (var i = 0; i < nodes.Count; i++)
|
||||||
|
{
|
||||||
|
lastPinnedNodePositions.Add(nodes[i] != null ? nodes[i].Position : Vector3.zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRestLengths.Clear();
|
||||||
|
for (var i = 0; i < restLengths.Count; i++)
|
||||||
|
{
|
||||||
|
lastRestLengths.Add(restLengths[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WakeUp()
|
||||||
|
{
|
||||||
|
isSleeping = false;
|
||||||
|
stableFrameCounter = 0;
|
||||||
|
accumulatedTime = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
private void ApplyToRenderer(IReadOnlyList<FishingLineNode> nodes)
|
private void ApplyToRenderer(IReadOnlyList<FishingLineNode> nodes)
|
||||||
{
|
{
|
||||||
lineRenderer.positionCount = positions.Count;
|
lineRenderer.positionCount = positions.Count;
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ namespace F2RopeLine2.FishingLine
|
|||||||
private readonly List<FishingLineNode> orderedNodes = new();
|
private readonly List<FishingLineNode> orderedNodes = 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> runtimeVirtualNodes = 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 Transform virtualNodeRoot;
|
||||||
@@ -68,16 +69,24 @@ namespace F2RopeLine2.FishingLine
|
|||||||
|
|
||||||
public int LogicalNodeCount => logicalNodes?.Length ?? 0;
|
public int LogicalNodeCount => logicalNodes?.Length ?? 0;
|
||||||
|
|
||||||
public int RuntimeVirtualNodeCount => runtimeVirtualNodes.Count;
|
public int RuntimeVirtualNodeCount => firstSegmentVirtualPool.Count + otherSegmentVirtualPool.Count;
|
||||||
|
|
||||||
public int ActiveRuntimeVirtualNodeCount
|
public int ActiveRuntimeVirtualNodeCount
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var activeCount = 0;
|
var activeCount = 0;
|
||||||
for (var i = 0; i < runtimeVirtualNodes.Count; i++)
|
for (var i = 0; i < firstSegmentVirtualPool.Count; i++)
|
||||||
{
|
{
|
||||||
if (runtimeVirtualNodes[i] != null && runtimeVirtualNodes[i].gameObject.activeSelf)
|
if (firstSegmentVirtualPool[i] != null && firstSegmentVirtualPool[i].gameObject.activeSelf)
|
||||||
|
{
|
||||||
|
activeCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < otherSegmentVirtualPool.Count; i++)
|
||||||
|
{
|
||||||
|
if (otherSegmentVirtualPool[i] != null && otherSegmentVirtualPool[i].gameObject.activeSelf)
|
||||||
{
|
{
|
||||||
activeCount++;
|
activeCount++;
|
||||||
}
|
}
|
||||||
@@ -350,29 +359,39 @@ namespace F2RopeLine2.FishingLine
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var expectedVirtualCount = 0;
|
|
||||||
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);
|
||||||
expectedVirtualCount += segmentLayouts[segmentIndex].VirtualNodeCount;
|
if (segmentIndex == 0)
|
||||||
|
{
|
||||||
|
firstSegmentVirtualCount = segmentLayouts[segmentIndex].VirtualNodeCount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
otherSegmentVirtualCount += segmentLayouts[segmentIndex].VirtualNodeCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResizeRuntimeVirtualNodePool(expectedVirtualCount);
|
EnsureFirstSegmentVirtualPool(firstSegmentVirtualCount);
|
||||||
|
EnsureOtherSegmentVirtualPool(otherSegmentVirtualCount);
|
||||||
|
|
||||||
orderedNodes.Add(logicalNodes[0]);
|
orderedNodes.Add(logicalNodes[0]);
|
||||||
pinnedIndices.Add(0);
|
pinnedIndices.Add(0);
|
||||||
|
|
||||||
var virtualNodeCursor = 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];
|
var layout = segmentLayouts[segmentIndex];
|
||||||
|
var segmentVirtualNodes = GetSegmentVirtualNodes(segmentIndex, layout.VirtualNodeCount, ref otherVirtualCursor);
|
||||||
|
|
||||||
for (var virtualIndex = 0; virtualIndex < layout.VirtualNodeCount; virtualIndex++)
|
for (var virtualIndex = 0; virtualIndex < segmentVirtualNodes.Count; virtualIndex++)
|
||||||
{
|
{
|
||||||
var virtualNode = runtimeVirtualNodes[virtualNodeCursor++];
|
var virtualNode = segmentVirtualNodes[virtualIndex];
|
||||||
virtualNode.SetRuntimeVirtual(true, orderedNodes.Count);
|
virtualNode.SetRuntimeVirtual(true, orderedNodes.Count);
|
||||||
virtualNode.name = $"VirtualNode_{segmentIndex}_{virtualIndex}";
|
virtualNode.name = $"VirtualNode_{segmentIndex}_{virtualIndex}";
|
||||||
virtualNode.transform.SetParent(virtualNodeRoot, false);
|
virtualNode.transform.SetParent(virtualNodeRoot, false);
|
||||||
@@ -389,10 +408,11 @@ namespace F2RopeLine2.FishingLine
|
|||||||
orderedNodes.Add(toNode);
|
orderedNodes.Add(toNode);
|
||||||
pinnedIndices.Add(orderedNodes.Count - 1);
|
pinnedIndices.Add(orderedNodes.Count - 1);
|
||||||
|
|
||||||
PlaceVirtualNodesBetween(fromNode, toNode, layout, segmentIndex);
|
PlaceVirtualNodesBetween(fromNode, toNode, layout, segmentVirtualNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
DisableUnusedVirtualNodes(expectedVirtualCount);
|
DisableUnusedFirstSegmentVirtualNodes(firstSegmentVirtualCount);
|
||||||
|
DisableUnusedOtherSegmentVirtualNodes(otherSegmentVirtualCount);
|
||||||
UpdateJointLimitsFromConfig();
|
UpdateJointLimitsFromConfig();
|
||||||
chainDirty = false;
|
chainDirty = false;
|
||||||
}
|
}
|
||||||
@@ -464,7 +484,7 @@ namespace F2RopeLine2.FishingLine
|
|||||||
FishingLineNode fromNode,
|
FishingLineNode fromNode,
|
||||||
FishingLineNode toNode,
|
FishingLineNode toNode,
|
||||||
SegmentLayout layout,
|
SegmentLayout layout,
|
||||||
int segmentIndex)
|
IReadOnlyList<FishingLineNode> segmentVirtualNodes)
|
||||||
{
|
{
|
||||||
if (layout.VirtualNodeCount == 0)
|
if (layout.VirtualNodeCount == 0)
|
||||||
{
|
{
|
||||||
@@ -476,55 +496,100 @@ namespace F2RopeLine2.FishingLine
|
|||||||
var normalizedDirection = distance > 0.0001f ? direction / distance : Vector3.down;
|
var normalizedDirection = distance > 0.0001f ? direction / distance : Vector3.down;
|
||||||
|
|
||||||
var accumulatedDistance = 0f;
|
var accumulatedDistance = 0f;
|
||||||
var runtimeBaseIndex = GetVirtualBaseIndexForSegment(segmentIndex);
|
|
||||||
for (var i = 0; i < layout.VirtualNodeCount; i++)
|
for (var i = 0; i < layout.VirtualNodeCount; i++)
|
||||||
{
|
{
|
||||||
accumulatedDistance += layout.GapLengths[i];
|
accumulatedDistance += layout.GapLengths[i];
|
||||||
var position = fromNode.Position + normalizedDirection * accumulatedDistance;
|
var position = fromNode.Position + normalizedDirection * accumulatedDistance;
|
||||||
var virtualNode = runtimeVirtualNodes[runtimeBaseIndex + i];
|
var virtualNode = segmentVirtualNodes[i];
|
||||||
virtualNode.SetVisualPosition(position);
|
var wasActive = virtualNode.gameObject.activeSelf;
|
||||||
|
if (!Application.isPlaying || !wasActive)
|
||||||
|
{
|
||||||
|
virtualNode.SetVisualPosition(position);
|
||||||
|
}
|
||||||
|
|
||||||
virtualNode.gameObject.SetActive(true);
|
virtualNode.gameObject.SetActive(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetVirtualBaseIndexForSegment(int segmentIndex)
|
private List<FishingLineNode> GetSegmentVirtualNodes(int segmentIndex, int virtualCount, ref int otherVirtualCursor)
|
||||||
{
|
{
|
||||||
var index = 0;
|
var result = new List<FishingLineNode>(virtualCount);
|
||||||
for (var i = 0; i < segmentIndex; i++)
|
if (virtualCount <= 0)
|
||||||
{
|
{
|
||||||
index += BuildSegmentLayout(i).VirtualNodeCount;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return index;
|
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 ResizeRuntimeVirtualNodePool(int targetCount)
|
private void EnsureFirstSegmentVirtualPool(int targetCount)
|
||||||
{
|
{
|
||||||
EnsureVirtualNodeRoot();
|
EnsureVirtualNodeRoot();
|
||||||
|
|
||||||
while (runtimeVirtualNodes.Count < targetCount)
|
while (firstSegmentVirtualPool.Count < targetCount)
|
||||||
{
|
{
|
||||||
var node = CreateRuntimeVirtualNode(runtimeVirtualNodes.Count);
|
var node = CreateRuntimeVirtualNode($"FirstSegmentVirtualNode_{firstSegmentVirtualPool.Count}");
|
||||||
runtimeVirtualNodes.Add(node);
|
firstSegmentVirtualPool.Add(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private FishingLineNode CreateRuntimeVirtualNode(int index)
|
private void EnsureOtherSegmentVirtualPool(int targetCount)
|
||||||
{
|
{
|
||||||
var runtimeObject = new GameObject($"VirtualNode_{index}");
|
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);
|
runtimeObject.transform.SetParent(virtualNodeRoot, false);
|
||||||
|
|
||||||
var node = runtimeObject.AddComponent<FishingLineNode>();
|
var node = runtimeObject.AddComponent<FishingLineNode>();
|
||||||
node.SetRuntimeVirtual(true, index);
|
node.SetRuntimeVirtual(true, -1);
|
||||||
runtimeObject.hideFlags = HideFlags.DontSave;
|
runtimeObject.hideFlags = HideFlags.DontSave;
|
||||||
|
runtimeObject.SetActive(false);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisableUnusedVirtualNodes(int usedCount)
|
private void DisableUnusedFirstSegmentVirtualNodes(int usedCount)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < runtimeVirtualNodes.Count; i++)
|
for (var i = 0; i < firstSegmentVirtualPool.Count; i++)
|
||||||
{
|
{
|
||||||
var node = runtimeVirtualNodes[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;
|
var active = i < usedCount;
|
||||||
if (node != null)
|
if (node != null)
|
||||||
{
|
{
|
||||||
@@ -535,17 +600,24 @@ namespace F2RopeLine2.FishingLine
|
|||||||
|
|
||||||
private void DestroyAllRuntimeVirtualNodes()
|
private void DestroyAllRuntimeVirtualNodes()
|
||||||
{
|
{
|
||||||
for (var i = runtimeVirtualNodes.Count - 1; i >= 0; i--)
|
for (var i = firstSegmentVirtualPool.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
DestroyNodeObject(runtimeVirtualNodes[i]);
|
DestroyNodeObject(firstSegmentVirtualPool[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
runtimeVirtualNodes.Clear();
|
for (var i = otherSegmentVirtualPool.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
DestroyNodeObject(otherSegmentVirtualPool[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
firstSegmentVirtualPool.Clear();
|
||||||
|
otherSegmentVirtualPool.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CleanupDeadReferences()
|
private void CleanupDeadReferences()
|
||||||
{
|
{
|
||||||
runtimeVirtualNodes.RemoveAll(node => node == null);
|
firstSegmentVirtualPool.RemoveAll(node => node == null);
|
||||||
|
otherSegmentVirtualPool.RemoveAll(node => node == null);
|
||||||
runtimeJoints.RemoveAll(joint => joint == null);
|
runtimeJoints.RemoveAll(joint => joint == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,13 +20,6 @@ namespace F2RopeLine2.FishingLine
|
|||||||
[Header("Input")]
|
[Header("Input")]
|
||||||
[SerializeField] private KeyCode extendKey = KeyCode.UpArrow;
|
[SerializeField] private KeyCode extendKey = KeyCode.UpArrow;
|
||||||
[SerializeField] private KeyCode retractKey = KeyCode.DownArrow;
|
[SerializeField] private KeyCode retractKey = KeyCode.DownArrow;
|
||||||
[SerializeField] private KeyCode rebuildKey = KeyCode.R;
|
|
||||||
|
|
||||||
[Header("Debug")]
|
|
||||||
[SerializeField] private bool showGui = true;
|
|
||||||
[SerializeField] private bool showNodeDetails = true;
|
|
||||||
[SerializeField] private bool showSegmentDetails = true;
|
|
||||||
[SerializeField] private Vector2 detailScroll = Vector2.zero;
|
|
||||||
|
|
||||||
private float targetFirstSegmentLength;
|
private float targetFirstSegmentLength;
|
||||||
|
|
||||||
@@ -75,96 +68,6 @@ namespace F2RopeLine2.FishingLine
|
|||||||
solver.SetFirstSegmentLength(targetFirstSegmentLength);
|
solver.SetFirstSegmentLength(targetFirstSegmentLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Input.GetKeyDown(rebuildKey))
|
|
||||||
{
|
|
||||||
solver.RebuildNow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnGUI()
|
|
||||||
{
|
|
||||||
if (!showGui || solver == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.BeginArea(new Rect(16f, 16f, 280f, 180f), GUI.skin.box);
|
|
||||||
GUILayout.Label("Fishing Line Test");
|
|
||||||
GUILayout.Label($"First Segment Length: {solver.FirstSegmentLength:F2} m");
|
|
||||||
GUILayout.Label($"Total Line Length: {solver.TotalLineLength:F2} m");
|
|
||||||
GUILayout.Label($"Chain: {(solver.IsChainDirty ? "Dirty" : "Ready")}");
|
|
||||||
GUILayout.Label($"Logical/Virtual/Ordered: {solver.LogicalNodeCount}/{solver.ActiveRuntimeVirtualNodeCount}/{solver.OrderedNodeCount}");
|
|
||||||
GUILayout.Label($"Keys: {extendKey} / {retractKey} / {rebuildKey}");
|
|
||||||
|
|
||||||
var nextLength = GUILayout.HorizontalSlider(
|
|
||||||
targetFirstSegmentLength,
|
|
||||||
minFirstSegmentLength,
|
|
||||||
maxFirstSegmentLength);
|
|
||||||
|
|
||||||
if (!Mathf.Approximately(nextLength, targetFirstSegmentLength))
|
|
||||||
{
|
|
||||||
targetFirstSegmentLength = nextLength;
|
|
||||||
solver.SetFirstSegmentLength(targetFirstSegmentLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GUILayout.Button("Rebuild Line"))
|
|
||||||
{
|
|
||||||
solver.RebuildNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal();
|
|
||||||
if (GUILayout.Button("Retract"))
|
|
||||||
{
|
|
||||||
targetFirstSegmentLength = Mathf.Max(minFirstSegmentLength, targetFirstSegmentLength - 0.1f);
|
|
||||||
solver.SetFirstSegmentLength(targetFirstSegmentLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GUILayout.Button("Extend"))
|
|
||||||
{
|
|
||||||
targetFirstSegmentLength = Mathf.Min(maxFirstSegmentLength, targetFirstSegmentLength + 0.1f);
|
|
||||||
solver.SetFirstSegmentLength(targetFirstSegmentLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
GUILayout.EndArea();
|
|
||||||
|
|
||||||
GUILayout.BeginArea(new Rect(16f, 204f, 420f, 320f), GUI.skin.box);
|
|
||||||
GUILayout.Label("Fishing Line Debug");
|
|
||||||
if (solver.TryGetComponent<FishingLineRenderer>(out var renderer))
|
|
||||||
{
|
|
||||||
GUILayout.Label($"Render Samples: {renderer.SampleCount}");
|
|
||||||
GUILayout.Label($"Rendered Length: {renderer.CurrentRenderedLength:F2} m");
|
|
||||||
}
|
|
||||||
|
|
||||||
detailScroll = GUILayout.BeginScrollView(detailScroll, GUILayout.Width(404f), GUILayout.Height(250f));
|
|
||||||
|
|
||||||
if (showNodeDetails)
|
|
||||||
{
|
|
||||||
GUILayout.Label("Nodes");
|
|
||||||
for (var i = 0; i < solver.OrderedNodeCount; i++)
|
|
||||||
{
|
|
||||||
if (!solver.TryGetOrderedNode(i, out var node))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.Label($"#{i} {node.GetDebugSummary()}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showSegmentDetails)
|
|
||||||
{
|
|
||||||
GUILayout.Label("Segments");
|
|
||||||
for (var i = 0; i < solver.SegmentCount; i++)
|
|
||||||
{
|
|
||||||
var rest = solver.RestLengths[i];
|
|
||||||
var actual = solver.GetActualDistance(i);
|
|
||||||
GUILayout.Label($"S{i} rest:{rest:F3}m actual:{actual:F3}m diff:{(actual - rest):F3}m");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.EndScrollView();
|
|
||||||
GUILayout.EndArea();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ PlayerSettings:
|
|||||||
androidApplicationEntry: 2
|
androidApplicationEntry: 2
|
||||||
defaultIsNativeResolution: 1
|
defaultIsNativeResolution: 1
|
||||||
macRetinaSupport: 1
|
macRetinaSupport: 1
|
||||||
runInBackground: 0
|
runInBackground: 1
|
||||||
muteOtherAudioSources: 0
|
muteOtherAudioSources: 0
|
||||||
Prepare IOS For Recording: 0
|
Prepare IOS For Recording: 0
|
||||||
Force IOS Speakers When Recording: 0
|
Force IOS Speakers When Recording: 0
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
%TAG !u! tag:unity3d.com,2011:
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
--- !u!78 &1
|
--- !u!78 &1
|
||||||
TagManager:
|
TagManager:
|
||||||
serializedVersion: 2
|
serializedVersion: 3
|
||||||
tags: []
|
tags: []
|
||||||
layers:
|
layers:
|
||||||
- Default
|
- Default
|
||||||
@@ -11,7 +11,7 @@ TagManager:
|
|||||||
-
|
-
|
||||||
- Water
|
- Water
|
||||||
- UI
|
- UI
|
||||||
-
|
- Terrain
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
@@ -50,27 +50,4 @@ TagManager:
|
|||||||
- Light Layer 5
|
- Light Layer 5
|
||||||
- Light Layer 6
|
- Light Layer 6
|
||||||
- Light Layer 7
|
- Light Layer 7
|
||||||
-
|
m_MigratedRenderPipelines: []
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
|
|||||||
Reference in New Issue
Block a user