鱼线性能优化

This commit is contained in:
2026-02-25 23:46:50 +08:00
parent ceaef328e3
commit adbd097fd5
2 changed files with 186 additions and 148 deletions

View File

@@ -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

View File

@@ -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 (注意:数组固定为 maxPhysicsNodesphysicsNodes 表示有效长度) // 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 cachesexp 比较贵,但这里每 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()