鱼线性能优化
This commit is contained in:
@@ -47,13 +47,14 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
startAnchor: {fileID: 0}
|
startAnchor: {fileID: 0}
|
||||||
endAnchor: {fileID: 54298866000586118}
|
endAnchor: {fileID: 54298866000586118}
|
||||||
|
LineMultiple: 1
|
||||||
physicsSegmentLen: 0.1
|
physicsSegmentLen: 0.1
|
||||||
minPhysicsNodes: 2
|
minPhysicsNodes: 2
|
||||||
maxPhysicsNodes: 200
|
maxPhysicsNodes: 200
|
||||||
gravityStrength: 6
|
gravityStrength: 6
|
||||||
velocityDampen: 0.95
|
velocityDampen: 0.95
|
||||||
stiffness: 0.8
|
stiffness: 0.8
|
||||||
iterations: 20
|
iterations: 10
|
||||||
initialLength: 0
|
initialLength: 0
|
||||||
lengthSmoothTime: 0.15
|
lengthSmoothTime: 0.15
|
||||||
lengthChangeVelocityKill: 0.4
|
lengthChangeVelocityKill: 0.4
|
||||||
@@ -662,13 +663,14 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
startAnchor: {fileID: 54298866000586118}
|
startAnchor: {fileID: 54298866000586118}
|
||||||
endAnchor: {fileID: 54679398375713381}
|
endAnchor: {fileID: 54679398375713381}
|
||||||
|
LineMultiple: 1
|
||||||
physicsSegmentLen: 0.2
|
physicsSegmentLen: 0.2
|
||||||
minPhysicsNodes: 2
|
minPhysicsNodes: 2
|
||||||
maxPhysicsNodes: 120
|
maxPhysicsNodes: 120
|
||||||
gravityStrength: 6
|
gravityStrength: 6
|
||||||
velocityDampen: 0.95
|
velocityDampen: 0.95
|
||||||
stiffness: 0.8
|
stiffness: 0.8
|
||||||
iterations: 20
|
iterations: 10
|
||||||
initialLength: 0
|
initialLength: 0
|
||||||
lengthSmoothTime: 0.15
|
lengthSmoothTime: 0.15
|
||||||
lengthChangeVelocityKill: 0.6
|
lengthChangeVelocityKill: 0.6
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ public class Rope : MonoBehaviour
|
|||||||
[Header("Anchors")] [SerializeField] public Rigidbody startAnchor;
|
[Header("Anchors")] [SerializeField] public Rigidbody startAnchor;
|
||||||
[SerializeField] public Rigidbody endAnchor;
|
[SerializeField] public Rigidbody endAnchor;
|
||||||
|
|
||||||
|
/// <summary>鱼线宽度倍数</summary>
|
||||||
|
public int LineMultiple = 1;
|
||||||
|
|
||||||
[Header("Physics (Dynamic Nodes, Fixed Segment Len)")] [SerializeField, Min(0.01f), Tooltip("物理每段固定长度(越小越细致越耗)")]
|
[Header("Physics (Dynamic Nodes, Fixed Segment Len)")] [SerializeField, Min(0.01f), Tooltip("物理每段固定长度(越小越细致越耗)")]
|
||||||
private float physicsSegmentLen = 0.15f;
|
private float physicsSegmentLen = 0.15f;
|
||||||
|
|
||||||
@@ -65,7 +68,7 @@ public class Rope : MonoBehaviour
|
|||||||
[SerializeField, Tooltip("是否使用 Catmull-Rom 平滑(开启更顺,但更耗)")]
|
[SerializeField, Tooltip("是否使用 Catmull-Rom 平滑(开启更顺,但更耗)")]
|
||||||
private bool smooth = true;
|
private bool smooth = true;
|
||||||
|
|
||||||
[SerializeField, Min(0.0001f)] private float lineWidth = 0.002f;
|
[SerializeField, Min(0.0001f)] private float lineWidth = 0.001f;
|
||||||
|
|
||||||
[Header("Air Drag (Stable)")] [SerializeField, Range(0f, 5f), Tooltip("空气阻力(Y向),指数衰减,越大越不飘")]
|
[Header("Air Drag (Stable)")] [SerializeField, Range(0f, 5f), Tooltip("空气阻力(Y向),指数衰减,越大越不飘")]
|
||||||
private float airDrag = 0.9f;
|
private float airDrag = 0.9f;
|
||||||
@@ -75,14 +78,14 @@ public class Rope : MonoBehaviour
|
|||||||
|
|
||||||
private LineRenderer _lineRenderer;
|
private LineRenderer _lineRenderer;
|
||||||
|
|
||||||
// physics (注意:数组固定为 maxPhysicsNodes,physicsNodes 表示有效长度)
|
// physics
|
||||||
private int _physicsNodes;
|
private int _physicsNodes;
|
||||||
private Vector3[] _pCurr;
|
private Vector3[] _pCurr;
|
||||||
private Vector3[] _pPrev;
|
private Vector3[] _pPrev;
|
||||||
|
|
||||||
// render
|
// render (一次性分配到最大,后续不再 new)
|
||||||
private Vector3[] _rPoints;
|
private Vector3[] _rPoints;
|
||||||
private int _rCountCached = -1;
|
private int _rCapacity;
|
||||||
|
|
||||||
private Vector3 _gravity;
|
private Vector3 _gravity;
|
||||||
|
|
||||||
@@ -97,17 +100,47 @@ public class Rope : MonoBehaviour
|
|||||||
// node stability
|
// node stability
|
||||||
private int _lastDesiredNodes = 0;
|
private int _lastDesiredNodes = 0;
|
||||||
|
|
||||||
|
// caches
|
||||||
|
private Transform _startTr;
|
||||||
|
private Transform _endTr;
|
||||||
|
|
||||||
|
// precomputed
|
||||||
|
private float _dt;
|
||||||
|
private float _dt2;
|
||||||
|
private float _kY;
|
||||||
|
private float _kXZ;
|
||||||
|
|
||||||
private FRod _rod;
|
private FRod _rod;
|
||||||
public void Init(FRod rod) => _rod = rod;
|
public void Init(FRod rod) => _rod = rod;
|
||||||
|
|
||||||
|
// Catmull t caches(只缓存 idle/moving 两档,减少每帧重复乘法)
|
||||||
|
private struct TCaches
|
||||||
|
{
|
||||||
|
public float[] t;
|
||||||
|
public float[] t2;
|
||||||
|
public float[] t3;
|
||||||
|
}
|
||||||
|
private TCaches _tIdle;
|
||||||
|
private TCaches _tMoving;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_lineRenderer = GetComponent<LineRenderer>();
|
_lineRenderer = GetComponent<LineRenderer>();
|
||||||
_gravity = new Vector3(0f, -gravityStrength, 0f);
|
_gravity = new Vector3(0f, -gravityStrength, 0f);
|
||||||
|
|
||||||
|
_startTr = startAnchor ? startAnchor.transform : null;
|
||||||
|
_endTr = endAnchor ? endAnchor.transform : null;
|
||||||
|
|
||||||
InitLengthSystem();
|
InitLengthSystem();
|
||||||
AllocateAndInitNodes();
|
AllocateAndInitNodes();
|
||||||
RebuildRenderBufferIfNeeded(renderSubdivisionsIdle);
|
|
||||||
|
// ✅ 渲染点一次性分配到最大: (maxNodes-1)*idle + 1
|
||||||
|
int maxSubdiv = Mathf.Max(1, renderSubdivisionsIdle);
|
||||||
|
_rCapacity = (maxPhysicsNodes - 1) * maxSubdiv + 1;
|
||||||
|
_rPoints = new Vector3[_rCapacity];
|
||||||
|
|
||||||
|
BuildTCaches(renderSubdivisionsIdle, ref _tIdle);
|
||||||
|
BuildTCaches(renderSubdivisionsMoving, ref _tMoving);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
@@ -127,7 +160,6 @@ public class Rope : MonoBehaviour
|
|||||||
|
|
||||||
headMinLen = Mathf.Max(headMinLen, 0.0001f);
|
headMinLen = Mathf.Max(headMinLen, 0.0001f);
|
||||||
nodeHysteresis = Mathf.Max(0f, nodeHysteresis);
|
nodeHysteresis = Mathf.Max(0f, nodeHysteresis);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitLengthSystem()
|
private void InitLengthSystem()
|
||||||
@@ -141,7 +173,6 @@ public class Rope : MonoBehaviour
|
|||||||
{
|
{
|
||||||
_physicsNodes = Mathf.Clamp(ComputeDesiredNodesStable(_currentLength), 2, maxPhysicsNodes);
|
_physicsNodes = Mathf.Clamp(ComputeDesiredNodesStable(_currentLength), 2, maxPhysicsNodes);
|
||||||
|
|
||||||
// ✅ 永久分配到最大,运行时不再 new,避免GC卡顿
|
|
||||||
_pCurr = new Vector3[maxPhysicsNodes];
|
_pCurr = new Vector3[maxPhysicsNodes];
|
||||||
_pPrev = new Vector3[maxPhysicsNodes];
|
_pPrev = new Vector3[maxPhysicsNodes];
|
||||||
|
|
||||||
@@ -181,10 +212,7 @@ public class Rope : MonoBehaviour
|
|||||||
if (desired == _lastDesiredNodes)
|
if (desired == _lastDesiredNodes)
|
||||||
return desired;
|
return desired;
|
||||||
|
|
||||||
// 边界(lastDesiredNodes 对应的长度临界)
|
|
||||||
float boundary = (_lastDesiredNodes - 1) * physicsSegmentLen;
|
float boundary = (_lastDesiredNodes - 1) * physicsSegmentLen;
|
||||||
|
|
||||||
// 在临界附近就不切换,避免来回跳
|
|
||||||
if (Mathf.Abs(lengthMeters - boundary) < nodeHysteresis)
|
if (Mathf.Abs(lengthMeters - boundary) < nodeHysteresis)
|
||||||
return _lastDesiredNodes;
|
return _lastDesiredNodes;
|
||||||
|
|
||||||
@@ -192,11 +220,7 @@ public class Rope : MonoBehaviour
|
|||||||
return desired;
|
return desired;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetTargetLength(float lengthMeters)
|
public void SetTargetLength(float lengthMeters) => _targetLength = Mathf.Max(0f, lengthMeters);
|
||||||
{
|
|
||||||
_targetLength = Mathf.Max(0f, lengthMeters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public float GetCurrentLength() => _currentLength;
|
public float GetCurrentLength() => _currentLength;
|
||||||
public float GetTargetLength() => _targetLength;
|
public float GetTargetLength() => _targetLength;
|
||||||
|
|
||||||
@@ -204,29 +228,36 @@ public class Rope : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if (!startAnchor || !endAnchor) return;
|
if (!startAnchor || !endAnchor) return;
|
||||||
|
|
||||||
|
// cache dt
|
||||||
|
_dt = Time.fixedDeltaTime;
|
||||||
|
if (_dt < 1e-6f) _dt = 1e-6f;
|
||||||
|
_dt2 = _dt * _dt;
|
||||||
|
|
||||||
|
// gravity
|
||||||
_gravity.y = -gravityStrength;
|
_gravity.y = -gravityStrength;
|
||||||
|
|
||||||
|
// drag caches(exp 比较贵,但这里每 FixedUpdate 一次,OK)
|
||||||
|
_kY = Mathf.Exp(-airDrag * _dt);
|
||||||
|
_kXZ = Mathf.Exp(-airDragXZ * _dt);
|
||||||
|
|
||||||
UpdateLengthSmooth();
|
UpdateLengthSmooth();
|
||||||
UpdateNodesFromLength(); // ✅ 一次性增减,无GC
|
UpdateNodesFromLength();
|
||||||
UpdateHeadRestLenFromCurrentLength();
|
UpdateHeadRestLenFromCurrentLength();
|
||||||
|
|
||||||
// simulate
|
Simulate_VerletFast();
|
||||||
Simulate();
|
|
||||||
|
|
||||||
// 确保端点正确后再迭代
|
// anchors
|
||||||
LockAnchorsHard();
|
LockAnchorsHard();
|
||||||
|
|
||||||
// constraints
|
// constraints
|
||||||
for (int it = 0; it < iterations; it++)
|
for (int it = 0; it < iterations; it++)
|
||||||
SolveDistanceConstraints_HeadOnly();
|
SolveDistanceConstraints_HeadOnly_Fast();
|
||||||
|
|
||||||
// 迭代后锁一次足够
|
|
||||||
LockAnchorsHard();
|
LockAnchorsHard();
|
||||||
|
|
||||||
if (constrainToGround)
|
if (constrainToGround)
|
||||||
ConstrainToGround();
|
ConstrainToGround();
|
||||||
|
|
||||||
// 约束后再锁一次(保险)
|
|
||||||
LockAnchorsHard();
|
LockAnchorsHard();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,23 +265,23 @@ public class Rope : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if (!startAnchor || !endAnchor || _pCurr == null || _physicsNodes < 2) return;
|
if (!startAnchor || !endAnchor || _pCurr == null || _physicsNodes < 2) return;
|
||||||
|
|
||||||
// 端点“跟手”:渲染前强行同步 transform,消除慢一拍
|
|
||||||
int last = _physicsNodes - 1;
|
int last = _physicsNodes - 1;
|
||||||
Vector3 s = startAnchor.transform.position;
|
|
||||||
Vector3 e = endAnchor.transform.position;
|
// 用缓存 transform,避免多次属性链
|
||||||
|
Vector3 s = _startTr.position;
|
||||||
|
Vector3 e = _endTr.position;
|
||||||
|
|
||||||
_pCurr[0] = s;
|
_pCurr[0] = s;
|
||||||
_pPrev[0] = s;
|
_pPrev[0] = s;
|
||||||
_pCurr[last] = e;
|
_pCurr[last] = e;
|
||||||
_pPrev[last] = e;
|
_pPrev[last] = e;
|
||||||
|
|
||||||
DrawHighResLine();
|
DrawHighResLine_Fast();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateLengthSmooth()
|
private void UpdateLengthSmooth()
|
||||||
{
|
{
|
||||||
float minFeasible = 0.01f;
|
float minFeasible = 0.01f;
|
||||||
|
|
||||||
float desired = Mathf.Max(_targetLength, minFeasible);
|
float desired = Mathf.Max(_targetLength, minFeasible);
|
||||||
|
|
||||||
_currentLength = Mathf.SmoothDamp(
|
_currentLength = Mathf.SmoothDamp(
|
||||||
@@ -269,22 +300,12 @@ public class Rope : MonoBehaviour
|
|||||||
desired = Mathf.Clamp(desired, 2, maxPhysicsNodes);
|
desired = Mathf.Clamp(desired, 2, maxPhysicsNodes);
|
||||||
if (desired == _physicsNodes) return;
|
if (desired == _physicsNodes) return;
|
||||||
|
|
||||||
if (desired > _physicsNodes)
|
if (desired > _physicsNodes) AddNodesAtStart(desired - _physicsNodes);
|
||||||
{
|
else RemoveNodesAtStart(_physicsNodes - desired);
|
||||||
AddNodesAtStart(desired - _physicsNodes);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RemoveNodesAtStart(_physicsNodes - desired);
|
|
||||||
}
|
|
||||||
|
|
||||||
_physicsNodes = desired;
|
_physicsNodes = desired;
|
||||||
|
|
||||||
// 渲染缓存按最大 subdiv 预留即可
|
|
||||||
RebuildRenderBufferIfNeeded(renderSubdivisionsIdle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void AddNodesAtStart(int addCount)
|
private void AddNodesAtStart(int addCount)
|
||||||
{
|
{
|
||||||
if (addCount <= 0) return;
|
if (addCount <= 0) return;
|
||||||
@@ -294,35 +315,30 @@ public class Rope : MonoBehaviour
|
|||||||
addCount = newCount - oldCount;
|
addCount = newCount - oldCount;
|
||||||
if (addCount <= 0) return;
|
if (addCount <= 0) return;
|
||||||
|
|
||||||
// 把 [1..oldCount-1] 整体往后挪 addCount,给 [1..addCount] 腾位置
|
|
||||||
// oldCount-1 个元素(包含尾端锚点位,尾端后面会被锁)
|
|
||||||
Array.Copy(_pCurr, 1, _pCurr, 1 + addCount, oldCount - 1);
|
Array.Copy(_pCurr, 1, _pCurr, 1 + addCount, oldCount - 1);
|
||||||
Array.Copy(_pPrev, 1, _pPrev, 1 + addCount, oldCount - 1);
|
Array.Copy(_pPrev, 1, _pPrev, 1 + addCount, oldCount - 1);
|
||||||
|
|
||||||
Vector3 s = startAnchor.position;
|
Vector3 s = _startTr ? _startTr.position : startAnchor.position;
|
||||||
|
|
||||||
// 方向:用“挪完后的第一个动态点”指向来估计
|
|
||||||
Vector3 dir = Vector3.down;
|
Vector3 dir = Vector3.down;
|
||||||
int firstOld = 1 + addCount;
|
int firstOld = 1 + addCount;
|
||||||
if (firstOld < 1 + addCount + (oldCount - 2) && oldCount >= 2)
|
|
||||||
{
|
|
||||||
Vector3 toOld1 = (_pCurr[firstOld] - s);
|
|
||||||
if (toOld1.sqrMagnitude > 1e-6f) dir = toOld1.normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 继承速度
|
|
||||||
float dt = Time.fixedDeltaTime;
|
|
||||||
Vector3 inheritVel = Vector3.zero;
|
|
||||||
if (oldCount >= 2 && firstOld < maxPhysicsNodes)
|
if (oldCount >= 2 && firstOld < maxPhysicsNodes)
|
||||||
{
|
{
|
||||||
inheritVel = (_pCurr[firstOld] - _pPrev[firstOld]) / Mathf.Max(dt, 1e-6f);
|
Vector3 toOld1 = (_pCurr[firstOld] - s);
|
||||||
|
float sq = toOld1.sqrMagnitude;
|
||||||
|
if (sq > 1e-6f) dir = toOld1 / Mathf.Sqrt(sq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inherit displacement (Verlet)
|
||||||
|
Vector3 inheritDisp = Vector3.zero;
|
||||||
|
if (oldCount >= 2 && firstOld < maxPhysicsNodes)
|
||||||
|
inheritDisp = (_pCurr[firstOld] - _pPrev[firstOld]);
|
||||||
|
|
||||||
for (int k = 1; k <= addCount; k++)
|
for (int k = 1; k <= addCount; k++)
|
||||||
{
|
{
|
||||||
Vector3 pos = s + dir * (physicsSegmentLen * k);
|
Vector3 pos = s + dir * (physicsSegmentLen * k);
|
||||||
_pCurr[k] = pos;
|
_pCurr[k] = pos;
|
||||||
_pPrev[k] = pos - inheritVel * dt;
|
_pPrev[k] = pos - inheritDisp; // 保持动感
|
||||||
}
|
}
|
||||||
|
|
||||||
LockAnchorsHard();
|
LockAnchorsHard();
|
||||||
@@ -337,8 +353,6 @@ public class Rope : MonoBehaviour
|
|||||||
removeCount = oldCount - newCount;
|
removeCount = oldCount - newCount;
|
||||||
if (removeCount <= 0) return;
|
if (removeCount <= 0) return;
|
||||||
|
|
||||||
// 把 [1+removeCount .. 1+removeCount+(newCount-2)-1] 挪到 [1..newCount-2]
|
|
||||||
// 中间动态节点数量 = newCount-2
|
|
||||||
Array.Copy(_pCurr, 1 + removeCount, _pCurr, 1, newCount - 2);
|
Array.Copy(_pCurr, 1 + removeCount, _pCurr, 1, newCount - 2);
|
||||||
Array.Copy(_pPrev, 1 + removeCount, _pPrev, 1, newCount - 2);
|
Array.Copy(_pPrev, 1 + removeCount, _pPrev, 1, newCount - 2);
|
||||||
|
|
||||||
@@ -354,29 +368,27 @@ public class Rope : MonoBehaviour
|
|||||||
_headRestLen = Mathf.Clamp(_headRestLen, headMinLen, physicsSegmentLen * 1.5f);
|
_headRestLen = Mathf.Clamp(_headRestLen, headMinLen, physicsSegmentLen * 1.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Simulate()
|
/// <summary>
|
||||||
|
/// ✅ 更快的 Verlet:去掉 /dt 和 *dt 抵消的无效计算
|
||||||
|
/// </summary>
|
||||||
|
private void Simulate_VerletFast()
|
||||||
{
|
{
|
||||||
float dt = Time.fixedDeltaTime;
|
// displacement = curr - prev
|
||||||
float invDt = 1f / Mathf.Max(dt, 1e-6f);
|
// next = curr + displacement*drag*dampen + gravity*dt^2
|
||||||
|
|
||||||
// 指数衰减:更稳定好调
|
|
||||||
float kY = Mathf.Exp(-airDrag * dt);
|
|
||||||
float kXZ = Mathf.Exp(-airDragXZ * dt);
|
|
||||||
|
|
||||||
for (int i = 1; i < _physicsNodes - 1; i++)
|
for (int i = 1; i < _physicsNodes - 1; i++)
|
||||||
{
|
{
|
||||||
Vector3 vel = (_pCurr[i] - _pPrev[i]) * invDt;
|
Vector3 disp = _pCurr[i] - _pPrev[i];
|
||||||
|
|
||||||
vel.x *= kXZ;
|
disp.x *= _kXZ;
|
||||||
vel.z *= kXZ;
|
disp.z *= _kXZ;
|
||||||
vel.y *= kY;
|
disp.y *= _kY;
|
||||||
|
|
||||||
vel *= velocityDampen;
|
disp *= velocityDampen;
|
||||||
|
|
||||||
Vector3 next = _pCurr[i] + vel * dt;
|
Vector3 next = _pCurr[i] + disp + _gravity * _dt2;
|
||||||
|
|
||||||
_pPrev[i] = _pCurr[i];
|
_pPrev[i] = _pCurr[i];
|
||||||
_pCurr[i] = next + _gravity * (dt * dt);
|
_pCurr[i] = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,21 +396,25 @@ public class Rope : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if (!startAnchor || !endAnchor || _pCurr == null || _pPrev == null || _physicsNodes < 2) return;
|
if (!startAnchor || !endAnchor || _pCurr == null || _pPrev == null || _physicsNodes < 2) return;
|
||||||
|
|
||||||
float dt = Time.fixedDeltaTime;
|
Vector3 s = _startTr ? _startTr.position : startAnchor.position;
|
||||||
Vector3 s = startAnchor.position;
|
Vector3 e = _endTr ? _endTr.position : endAnchor.position;
|
||||||
Vector3 e = endAnchor.position;
|
|
||||||
|
|
||||||
_pCurr[0] = s;
|
_pCurr[0] = s;
|
||||||
_pPrev[0] = s - startAnchor.linearVelocity * dt;
|
_pPrev[0] = s - startAnchor.linearVelocity * _dt;
|
||||||
|
|
||||||
int last = _physicsNodes - 1;
|
int last = _physicsNodes - 1;
|
||||||
_pCurr[last] = e;
|
_pCurr[last] = e;
|
||||||
_pPrev[last] = e - endAnchor.linearVelocity * dt;
|
_pPrev[last] = e - endAnchor.linearVelocity * _dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SolveDistanceConstraints_HeadOnly()
|
/// <summary>
|
||||||
|
/// ✅ 约束:减少临时变量、用 sqrMagnitude + invDist
|
||||||
|
/// </summary>
|
||||||
|
private void SolveDistanceConstraints_HeadOnly_Fast()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _physicsNodes - 1; i++)
|
int last = _physicsNodes - 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < last; i++)
|
||||||
{
|
{
|
||||||
float rest = (i == 0) ? _headRestLen : physicsSegmentLen;
|
float rest = (i == 0) ? _headRestLen : physicsSegmentLen;
|
||||||
|
|
||||||
@@ -406,131 +422,151 @@ public class Rope : MonoBehaviour
|
|||||||
Vector3 b = _pCurr[i + 1];
|
Vector3 b = _pCurr[i + 1];
|
||||||
|
|
||||||
Vector3 delta = b - a;
|
Vector3 delta = b - a;
|
||||||
float dist = delta.magnitude;
|
float sq = delta.sqrMagnitude;
|
||||||
if (dist < 1e-6f) continue;
|
if (sq < 1e-12f) continue;
|
||||||
|
|
||||||
float diff = (dist - rest) / dist;
|
float dist = Mathf.Sqrt(sq);
|
||||||
Vector3 corr = delta * diff * stiffness;
|
float diff = (dist - rest) / dist; // = 1 - rest/dist
|
||||||
|
Vector3 corr = delta * (diff * stiffness);
|
||||||
|
|
||||||
if (i != 0)
|
// i==0 锚点固定;last 锚点固定
|
||||||
_pCurr[i] += corr * 0.5f;
|
if (i != 0) _pCurr[i] = a + corr * 0.5f;
|
||||||
|
if (i + 1 != last) _pCurr[i + 1] = b - corr * 0.5f;
|
||||||
if (i + 1 != _physicsNodes - 1)
|
|
||||||
_pCurr[i + 1] -= corr * 0.5f;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConstrainToGround()
|
private void ConstrainToGround()
|
||||||
{
|
{
|
||||||
|
if (groundMask == 0) return;
|
||||||
|
|
||||||
|
// RaycastHit 是 struct,这里不会 GC
|
||||||
for (int i = 1; i < _physicsNodes - 1; i++)
|
for (int i = 1; i < _physicsNodes - 1; i++)
|
||||||
{
|
{
|
||||||
Vector3 p = _pCurr[i];
|
Vector3 p = _pCurr[i];
|
||||||
|
Vector3 origin = p + Vector3.up * groundCastHeight;
|
||||||
|
|
||||||
if (constrainToGround && groundMask != 0)
|
if (Physics.Raycast(origin, Vector3.down, out RaycastHit hit, groundCastDistance, groundMask,
|
||||||
|
QueryTriggerInteraction.Ignore))
|
||||||
{
|
{
|
||||||
Vector3 origin = p + Vector3.up * groundCastHeight;
|
float minY = hit.point.y + groundRadius;
|
||||||
if (Physics.Raycast(origin, Vector3.down, out RaycastHit hit, groundCastDistance, groundMask,
|
if (p.y < minY) p.y = minY;
|
||||||
QueryTriggerInteraction.Ignore))
|
|
||||||
{
|
|
||||||
float minY = hit.point.y + groundRadius;
|
|
||||||
if (p.y < minY) p.y = minY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_pCurr[i] = p;
|
_pCurr[i] = p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawHighResLine()
|
private void DrawHighResLine_Fast()
|
||||||
{
|
{
|
||||||
if (_pCurr == null || _physicsNodes < 2) return;
|
if (_pCurr == null || _physicsNodes < 2) return;
|
||||||
|
|
||||||
_lineRenderer.startWidth = lineWidth;
|
float w = lineWidth * LineMultiple;
|
||||||
_lineRenderer.endWidth = lineWidth;
|
_lineRenderer.startWidth = w;
|
||||||
|
_lineRenderer.endWidth = w;
|
||||||
|
|
||||||
if (!smooth)
|
if (!smooth)
|
||||||
{
|
{
|
||||||
_lineRenderer.positionCount = _physicsNodes;
|
_lineRenderer.positionCount = _physicsNodes;
|
||||||
// 只传有效段
|
|
||||||
// LineRenderer.SetPositions 会读数组的前 positionCount 个
|
|
||||||
_lineRenderer.SetPositions(_pCurr);
|
_lineRenderer.SetPositions(_pCurr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动态降 subdiv:甩动时降低点数,减少CPU开销
|
int subdiv = PickRenderSubdivisions_Fast();
|
||||||
int subdiv = PickRenderSubdivisions();
|
TCaches tc = (subdiv == renderSubdivisionsMoving) ? _tMoving : _tIdle;
|
||||||
RebuildRenderBufferIfNeeded(subdiv);
|
|
||||||
|
int needed = (_physicsNodes - 1) * subdiv + 1;
|
||||||
|
if (needed > _rCapacity)
|
||||||
|
{
|
||||||
|
// 理论上不该发生(_rCapacity 用 maxNodes & idle 分配)
|
||||||
|
// 保险扩容一次
|
||||||
|
_rCapacity = needed;
|
||||||
|
_rPoints = new Vector3[_rCapacity];
|
||||||
|
}
|
||||||
|
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
|
int last = _physicsNodes - 1;
|
||||||
|
|
||||||
for (int seg = 0; seg < _physicsNodes - 1; seg++)
|
for (int seg = 0; seg < last; seg++)
|
||||||
{
|
{
|
||||||
Vector3 p0 = _pCurr[Mathf.Max(seg - 1, 0)];
|
int i0 = seg - 1; if (i0 < 0) i0 = 0;
|
||||||
Vector3 p1 = _pCurr[seg];
|
int i1 = seg;
|
||||||
Vector3 p2 = _pCurr[seg + 1];
|
int i2 = seg + 1;
|
||||||
Vector3 p3 = _pCurr[Mathf.Min(seg + 2, _physicsNodes - 1)];
|
int i3 = seg + 2; if (i3 > last) i3 = last;
|
||||||
|
|
||||||
|
Vector3 p0 = _pCurr[i0];
|
||||||
|
Vector3 p1 = _pCurr[i1];
|
||||||
|
Vector3 p2 = _pCurr[i2];
|
||||||
|
Vector3 p3 = _pCurr[i3];
|
||||||
|
|
||||||
for (int s = 0; s < subdiv; s++)
|
for (int s = 0; s < subdiv; s++)
|
||||||
{
|
{
|
||||||
float t = s / (float)subdiv;
|
float t = tc.t[s];
|
||||||
Vector3 pt = CatmullRom_XZ_LinearY(p0, p1, p2, p3, t);
|
float t2 = tc.t2[s];
|
||||||
_rPoints[idx++] = pt;
|
float t3 = tc.t3[s];
|
||||||
|
|
||||||
|
// inline CatmullRom(少一次函数调用)
|
||||||
|
Vector3 cr =
|
||||||
|
0.5f * (
|
||||||
|
(2f * p1) +
|
||||||
|
(-p0 + p2) * t +
|
||||||
|
(2f * p0 - 5f * p1 + 4f * p2 - p3) * t2 +
|
||||||
|
(-p0 + 3f * p1 - 3f * p2 + p3) * t3
|
||||||
|
);
|
||||||
|
|
||||||
|
// Linear Y
|
||||||
|
cr.y = p1.y + (p2.y - p1.y) * t;
|
||||||
|
|
||||||
|
_rPoints[idx++] = cr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_rPoints[idx++] = _pCurr[_physicsNodes - 1];
|
_rPoints[idx++] = _pCurr[last];
|
||||||
|
|
||||||
_lineRenderer.positionCount = idx;
|
_lineRenderer.positionCount = idx;
|
||||||
_lineRenderer.SetPositions(_rPoints);
|
_lineRenderer.SetPositions(_rPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int PickRenderSubdivisions()
|
/// <summary>
|
||||||
|
/// ✅ 用 sqrMagnitude 比较阈值,避免 sqrt
|
||||||
|
/// </summary>
|
||||||
|
private int PickRenderSubdivisions_Fast()
|
||||||
{
|
{
|
||||||
int idle = Mathf.Max(1, renderSubdivisionsIdle);
|
int idle = Mathf.Max(1, renderSubdivisionsIdle);
|
||||||
int moving = Mathf.Max(1, renderSubdivisionsMoving);
|
int moving = Mathf.Max(1, renderSubdivisionsMoving);
|
||||||
|
|
||||||
// 计算平均速度(用 Verlet 差分)
|
float thr = movingSpeedThreshold;
|
||||||
float dt = Time.fixedDeltaTime;
|
float thrSq = (thr * _dt) * (thr * _dt); // 因为我们用 disp = curr-prev(单位是米/step),所以阈值要乘 dt
|
||||||
float invDt = 1f / Mathf.Max(dt, 1e-6f);
|
|
||||||
|
|
||||||
float sum = 0f;
|
float sumSq = 0f;
|
||||||
int count = Mathf.Max(1, _physicsNodes - 2);
|
int count = Mathf.Max(1, _physicsNodes - 2);
|
||||||
|
|
||||||
for (int i = 1; i < _physicsNodes - 1; i++)
|
for (int i = 1; i < _physicsNodes - 1; i++)
|
||||||
sum += (_pCurr[i] - _pPrev[i]).magnitude * invDt;
|
|
||||||
|
|
||||||
float avgSpeed = sum / count;
|
|
||||||
|
|
||||||
return (avgSpeed > movingSpeedThreshold) ? moving : idle;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RebuildRenderBufferIfNeeded(int subdiv)
|
|
||||||
{
|
|
||||||
int targetCount = (_physicsNodes - 1) * subdiv + 1;
|
|
||||||
if (_rPoints == null || _rCountCached != targetCount)
|
|
||||||
{
|
{
|
||||||
_rPoints = new Vector3[targetCount];
|
Vector3 disp = _pCurr[i] - _pPrev[i];
|
||||||
_rCountCached = targetCount;
|
sumSq += disp.sqrMagnitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float avgSq = sumSq / count;
|
||||||
|
|
||||||
|
return (avgSq > thrSq) ? moving : idle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Vector3 CatmullRom(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
|
private static void BuildTCaches(int subdiv, ref TCaches caches)
|
||||||
{
|
{
|
||||||
float t2 = t * t;
|
subdiv = Mathf.Max(1, subdiv);
|
||||||
float t3 = t2 * t;
|
caches.t = new float[subdiv];
|
||||||
|
caches.t2 = new float[subdiv];
|
||||||
|
caches.t3 = new float[subdiv];
|
||||||
|
|
||||||
return 0.5f * (
|
float inv = 1f / subdiv;
|
||||||
(2f * p1) +
|
for (int s = 0; s < subdiv; s++)
|
||||||
(-p0 + p2) * t +
|
{
|
||||||
(2f * p0 - 5f * p1 + 4f * p2 - p3) * t2 +
|
float t = s * inv;
|
||||||
(-p0 + 3f * p1 - 3f * p2 + p3) * t3
|
float t2 = t * t;
|
||||||
);
|
caches.t[s] = t;
|
||||||
}
|
caches.t2[s] = t2;
|
||||||
|
caches.t3[s] = t2 * t;
|
||||||
private static Vector3 CatmullRom_XZ_LinearY(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
|
}
|
||||||
{
|
|
||||||
Vector3 cr = CatmullRom(p0, p1, p2, p3, t);
|
|
||||||
cr.y = Mathf.Lerp(p1.y, p2.y, t);
|
|
||||||
return cr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDrawGizmosSelected()
|
private void OnDrawGizmosSelected()
|
||||||
|
|||||||
Reference in New Issue
Block a user