渲染和逻辑分离

This commit is contained in:
Bob.Song
2026-04-18 12:49:55 +08:00
parent 6797852481
commit 8f9d51cb60
8 changed files with 342 additions and 244 deletions

View File

@@ -355,7 +355,7 @@ GameObject:
- component: {fileID: 524034410} - component: {fileID: 524034410}
- component: {fileID: 524034409} - component: {fileID: 524034409}
- component: {fileID: 524034408} - component: {fileID: 524034408}
m_Layer: 0 m_Layer: 6
m_Name: Plane m_Name: Plane
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -468,6 +468,7 @@ GameObject:
- component: {fileID: 678145338} - component: {fileID: 678145338}
- component: {fileID: 678145337} - component: {fileID: 678145337}
- component: {fileID: 678145336} - component: {fileID: 678145336}
- component: {fileID: 678145340}
m_Layer: 0 m_Layer: 0
m_Name: RopeGenerator m_Name: RopeGenerator
m_TagString: Untagged m_TagString: Untagged
@@ -491,7 +492,7 @@ MonoBehaviour:
enabled: 1 enabled: 1
ropeStartPoint: {x: -3, y: 4, z: 0} ropeStartPoint: {x: -3, y: 4, z: 0}
ropeEndPoint: {x: 3, y: 4, z: 0} ropeEndPoint: {x: 3, y: 4, z: 0}
ropeResolution: 0.895 ropeResolution: 0.5
ropeRadius: 0.1 ropeRadius: 0.1
gravity: {x: 0, y: -9.81, z: 0} gravity: {x: 0, y: -9.81, z: 0}
airFriction: 0.1 airFriction: 0.1
@@ -505,7 +506,7 @@ MonoBehaviour:
enableCollisions: 1 enableCollisions: 1
collisionLayer: collisionLayer:
serializedVersion: 2 serializedVersion: 2
m_Bits: 4294967295 m_Bits: 64
enableSelfCollision: 0 enableSelfCollision: 0
initialPinPoints: initialPinPoints:
- nodeIndex: 0 - nodeIndex: 0
@@ -514,8 +515,7 @@ MonoBehaviour:
- nodeIndex: -1 - nodeIndex: -1
targetTransform: {fileID: 415232843} targetTransform: {fileID: 415232843}
localPos: {x: 0, y: 0, z: 0} localPos: {x: 0, y: 0, z: 0}
meshRadialSegments: 8 ropeRendererComponent: {fileID: 0}
meshTextureTiling: 1
--- !u!23 &678145337 --- !u!23 &678145337
MeshRenderer: MeshRenderer:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -588,6 +588,20 @@ Transform:
m_Children: [] m_Children: []
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &678145340
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 678145335}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: daa54ae922807734c889e8404bfd7b2e, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::AdvancedRope.RopeMeshRenderer
radialSegments: 8
textureTiling: 1
--- !u!1 &956472882 --- !u!1 &956472882
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -0,0 +1,10 @@
namespace AdvancedRope
{
public interface IRopeRenderer
{
void Bind(VerletRopeGenerator ropeGenerator);
void Rebuild();
void Render();
void Clear();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5fb18fbc92c2ecf49a7fcbb8fce7df36

View File

@@ -2,12 +2,13 @@
namespace AdvancedRope namespace AdvancedRope
{ {
public class RopeMesh [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class RopeMeshRenderer : MonoBehaviour, IRopeRenderer
{ {
private readonly int _radialSegments; [SerializeField] private int radialSegments = 8;
private readonly float _textureTiling; [SerializeField] private float textureTiling = 1f;
private VerletRopeGenerator _verletRopeGenerator; private VerletRopeGenerator _ropeGenerator;
private MeshFilter _meshFilter; private MeshFilter _meshFilter;
private Mesh _mesh; private Mesh _mesh;
private Vector3[] _vertices; private Vector3[] _vertices;
@@ -15,47 +16,76 @@ namespace AdvancedRope
private Vector2[] _uvs; private Vector2[] _uvs;
private float _radius; private float _radius;
public RopeMesh(MeshFilter meshFilter, VerletRopeGenerator verletRopeGenerator, int radialSegments = 8, float textureTiling = 1f) private void Awake()
{ {
_verletRopeGenerator = verletRopeGenerator; _meshFilter = GetComponent<MeshFilter>();
_meshFilter = meshFilter;
_radialSegments = radialSegments;
_textureTiling = textureTiling;
} }
public void SetupRopeMesh() public void Bind(VerletRopeGenerator ropeGenerator)
{ {
_mesh = new Mesh _ropeGenerator = ropeGenerator;
}
public void Rebuild()
{
SetupRopeMesh();
}
public void Render()
{
UpdateMeshVertices();
}
public void Clear()
{
ClearMesh();
}
private void SetupRopeMesh()
{
if (_ropeGenerator == null) return;
if (!_meshFilter) _meshFilter = GetComponent<MeshFilter>();
var segmentCount = Mathf.Max(3, radialSegments);
if (_mesh == null)
{ {
name = "ProceduralRope" _mesh = new Mesh
}; {
_mesh.MarkDynamic(); name = "ProceduralRope"
};
_mesh.MarkDynamic();
}
else
{
_mesh.Clear();
}
_meshFilter.sharedMesh = _mesh; _meshFilter.sharedMesh = _mesh;
_radius = _verletRopeGenerator.ropeRadius; _radius = _ropeGenerator.ropeRadius;
var nodes = _verletRopeGenerator.Nodes; var nodes = _ropeGenerator.Nodes;
var vertexCount = nodes.Count * (_radialSegments + 1) + 2; var vertexCount = nodes.Count * (segmentCount + 1) + 2;
var triCount = (nodes.Count - 1) * _radialSegments * 6; var triCount = (nodes.Count - 1) * segmentCount * 6;
var capTrisCount = (_radialSegments * 3) * 2; var capTrisCount = (segmentCount * 3) * 2;
_vertices = new Vector3[vertexCount]; _vertices = new Vector3[vertexCount];
_uvs = new Vector2[vertexCount]; _uvs = new Vector2[vertexCount];
_triangles = new int[triCount + capTrisCount]; _triangles = new int[triCount + capTrisCount];
var triIndex = 0; var triIndex = 0;
for (var i = 0; i < nodes.Count - 1; i++) for (var i = 0; i < nodes.Count - 1; i++)
{ {
for (var j = 0; j < _radialSegments; j++) for (var j = 0; j < segmentCount; j++)
{ {
var current = i * (_radialSegments + 1) + j; var current = i * (segmentCount + 1) + j;
var next = current + _radialSegments + 1; var next = current + segmentCount + 1;
_triangles[triIndex++] = current; _triangles[triIndex++] = current;
_triangles[triIndex++] = current + 1; _triangles[triIndex++] = current + 1;
_triangles[triIndex++] = next; _triangles[triIndex++] = next;
_triangles[triIndex++] = next; _triangles[triIndex++] = next;
_triangles[triIndex++] = current + 1; _triangles[triIndex++] = current + 1;
_triangles[triIndex++] = next + 1; _triangles[triIndex++] = next + 1;
@@ -64,21 +94,21 @@ namespace AdvancedRope
var startCenterIndex = vertexCount - 2; var startCenterIndex = vertexCount - 2;
var endCenterIndex = vertexCount - 1; var endCenterIndex = vertexCount - 1;
// Start Cap // Start Cap
for (var j = 0; j < _radialSegments; j++) for (var j = 0; j < segmentCount; j++)
{ {
var current = j; var current = j;
var next = j + 1; var next = j + 1;
_triangles[triIndex++] = startCenterIndex; _triangles[triIndex++] = startCenterIndex;
_triangles[triIndex++] = next; _triangles[triIndex++] = next;
_triangles[triIndex++] = current; _triangles[triIndex++] = current;
} }
// End Cap // End Cap
var lastRingStart = (nodes.Count - 1) * (_radialSegments + 1); var lastRingStart = (nodes.Count - 1) * (segmentCount + 1);
for (var j = 0; j < _radialSegments; j++) for (var j = 0; j < segmentCount; j++)
{ {
var current = lastRingStart + j; var current = lastRingStart + j;
var next = lastRingStart + j + 1; var next = lastRingStart + j + 1;
@@ -90,22 +120,36 @@ namespace AdvancedRope
_mesh.vertices = _vertices; _mesh.vertices = _vertices;
_mesh.triangles = _triangles; _mesh.triangles = _triangles;
_mesh.uv = _uvs;
_mesh.RecalculateBounds();
} }
public void UpdateMesh() private void ClearMesh()
{ {
UpdateMeshVertices(); if (_meshFilter)
_meshFilter.sharedMesh = null;
if (_mesh != null)
{
if (Application.isPlaying)
Destroy(_mesh);
else
DestroyImmediate(_mesh);
}
_mesh = null;
} }
public void ClearMesh()
{
_meshFilter.sharedMesh = null;
}
private void UpdateMeshVertices() private void UpdateMeshVertices()
{ {
var verletTransform = _verletRopeGenerator.transform; if (_ropeGenerator == null || _mesh == null) return;
var nodes = _verletRopeGenerator.Nodes;
if(nodes.Count < 2) return; var segmentCount = Mathf.Max(3, radialSegments);
var uvTiling = Mathf.Max(0.01f, textureTiling);
var verletTransform = _ropeGenerator.transform;
var nodes = _ropeGenerator.Nodes;
if (nodes.Count < 2) return;
var lastRotation = Quaternion.identity; var lastRotation = Quaternion.identity;
var lastForward = Vector3.zero; var lastForward = Vector3.zero;
@@ -117,9 +161,9 @@ namespace AdvancedRope
forward = (nodes[i + 1].position - nodes[i].position).normalized; forward = (nodes[i + 1].position - nodes[i].position).normalized;
else else
forward = (nodes[i].position - nodes[i - 1].position).normalized; forward = (nodes[i].position - nodes[i - 1].position).normalized;
if (forward == Vector3.zero) forward = Vector3.up; if (forward == Vector3.zero) forward = Vector3.up;
Quaternion rotation; Quaternion rotation;
if (i == 0) if (i == 0)
@@ -127,16 +171,16 @@ namespace AdvancedRope
else else
{ {
var swing = Quaternion.FromToRotation(lastForward, forward); var swing = Quaternion.FromToRotation(lastForward, forward);
rotation = swing * lastRotation; rotation = swing * lastRotation;
} }
lastRotation = rotation; lastRotation = rotation;
lastForward = forward; lastForward = forward;
for (var j = 0; j <= _radialSegments; j++) for (var j = 0; j <= segmentCount; j++)
{ {
var u = (float)j / _radialSegments; var u = (float)j / segmentCount;
var angle = u * Mathf.PI * 2; var angle = u * Mathf.PI * 2;
var x = Mathf.Cos(angle) * _radius; var x = Mathf.Cos(angle) * _radius;
@@ -146,24 +190,24 @@ namespace AdvancedRope
var worldPos = nodes[i].position + (rotation * localPos); var worldPos = nodes[i].position + (rotation * localPos);
var meshLocalPos = verletTransform.InverseTransformPoint(worldPos); var meshLocalPos = verletTransform.InverseTransformPoint(worldPos);
var vertIndex = i * (_radialSegments + 1) + j; var vertIndex = i * (segmentCount + 1) + j;
_vertices[vertIndex] = meshLocalPos; _vertices[vertIndex] = meshLocalPos;
var v = (float)i / (nodes.Count - 1) * _textureTiling; var v = (float)i / (nodes.Count - 1) * uvTiling;
_uvs[vertIndex] = new Vector2(u, v); _uvs[vertIndex] = new Vector2(u, v);
} }
} }
var startCenterIndex = _vertices.Length - 2; var startCenterIndex = _vertices.Length - 2;
var endCenterIndex = _vertices.Length - 1; var endCenterIndex = _vertices.Length - 1;
_vertices[startCenterIndex] = verletTransform.InverseTransformPoint(nodes[0].position); _vertices[startCenterIndex] = verletTransform.InverseTransformPoint(nodes[0].position);
_uvs[startCenterIndex] = new Vector2(0.5f, 0f); _uvs[startCenterIndex] = new Vector2(0.5f, 0f);
_vertices[endCenterIndex] = verletTransform.InverseTransformPoint(nodes[^1].position); _vertices[endCenterIndex] = verletTransform.InverseTransformPoint(nodes[^1].position);
_uvs[endCenterIndex] = new Vector2(0.5f, 1f * _textureTiling); _uvs[endCenterIndex] = new Vector2(0.5f, uvTiling);
_mesh.vertices = _vertices; _mesh.vertices = _vertices;
_mesh.uv = _uvs; _mesh.uv = _uvs;

View File

@@ -7,7 +7,6 @@ namespace AdvancedRope
/// <summary> /// <summary>
/// 用于生成并模拟基于 Verlet 积分绳索的基类。 /// 用于生成并模拟基于 Verlet 积分绳索的基类。
/// </summary> /// </summary>
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class VerletRopeGenerator : MonoBehaviour public class VerletRopeGenerator : MonoBehaviour
{ {
#region --- --- #region --- ---
@@ -24,7 +23,7 @@ namespace AdvancedRope
public RopeNode connectedNode; public RopeNode connectedNode;
public Vector3 connectionOffset; public Vector3 connectionOffset;
public Transform pinnedTransform; public Transform pinnedTransform;
public Vector3 pinLocalPos; public Vector3 pinLocalPos;
@@ -46,7 +45,7 @@ namespace AdvancedRope
pinnedTransform = t; pinnedTransform = t;
pinLocalPos = offset; pinLocalPos = offset;
} }
/// <summary> /// <summary>
/// 将该节点连接到另一条绳索节点,可选偏移量。 /// 将该节点连接到另一条绳索节点,可选偏移量。
/// </summary> /// </summary>
@@ -59,7 +58,7 @@ namespace AdvancedRope
isPinned = false; isPinned = false;
pinnedTransform = null; pinnedTransform = null;
} }
/// <summary> /// <summary>
/// 取消该节点的固定状态。 /// 取消该节点的固定状态。
/// </summary> /// </summary>
@@ -82,8 +81,7 @@ namespace AdvancedRope
[System.Serializable] [System.Serializable]
public struct PinConfiguration public struct PinConfiguration
{ {
[Tooltip("要固定的节点索引0 = 起点,-1 = 末尾)")] [Tooltip("要固定的节点索引0 = 起点,-1 = 末尾)")] public int nodeIndex;
public int nodeIndex;
public Transform targetTransform; public Transform targetTransform;
public Vector3 localPos; public Vector3 localPos;
} }
@@ -95,33 +93,29 @@ namespace AdvancedRope
//[Header("通用设置")] //[Header("通用设置")]
public bool initializeOnStart = false; public bool initializeOnStart = false;
public bool enabled = true; public bool enabled = true;
[Tooltip("绳索起点")] [Tooltip("绳索起点")] public Vector3 ropeStartPoint;
public Vector3 ropeStartPoint; [Tooltip("绳索终点(可选,若为空则绳索自由下垂)")] public Vector3 ropeEndPoint;
[Tooltip("绳索终点(可选,若为空则绳索自由下垂)")]
public Vector3 ropeEndPoint;
//[Header("分辨率与尺寸")] //[Header("分辨率与尺寸")]
[Range(0.01f, 1f)] [Range(0.01f, 1f)] [Tooltip("0.1 = 稀疏1.0 = 按半径可达到的最大密度")]
[Tooltip("0.1 = 稀疏1.0 = 按半径可达到的最大密度")]
public float ropeResolution = 0.5f; public float ropeResolution = 0.5f;
public float ropeRadius = 0.1f; public float ropeRadius = 0.1f;
//[Header("物理参数")] //[Header("物理参数")]
public Vector3 gravity = new Vector3(0, -9.81f, 0); public Vector3 gravity = new Vector3(0, -9.81f, 0);
[Range(0f, 1f)] public float airFriction = 0.1f; [Range(0f, 1f)] public float airFriction = 0.1f;
[Range(0f, 1f)] public float stiffness = 1.0f; [Range(0f, 1f)] public float stiffness = 1.0f;
[Tooltip("约束求解迭代次数。值越高越硬,但开销更大")] [Tooltip("约束求解迭代次数。值越高越硬,但开销更大")] public int constraintIterations = 8;
public int constraintIterations = 8; [Tooltip("绳索与刚体碰撞时施加给刚体的力")] public float collisionPushPower = 5f;
[Tooltip("绳索与刚体碰撞时施加给刚体的力")]
public float collisionPushPower = 5f;
[Range(0f, 1f)] public float contactFriction = 0.5f; [Range(0f, 1f)] public float contactFriction = 0.5f;
//[Header("耦合设置")] //[Header("耦合设置")]
public Rigidbody connectedBodyStart; public Rigidbody connectedBodyStart;
public Rigidbody connectedBodyEnd; public Rigidbody connectedBodyEnd;
public float rigidbodyInteractionForce = 10f; public float rigidbodyInteractionForce = 10f;
//[Header("碰撞设置")] //[Header("碰撞设置")]
public bool enableCollisions = true; public bool enableCollisions = true;
public LayerMask collisionLayer; public LayerMask collisionLayer;
@@ -129,65 +123,82 @@ namespace AdvancedRope
//[Header("初始设置")] //[Header("初始设置")]
public List<PinConfiguration> initialPinPoints; public List<PinConfiguration> initialPinPoints;
//[Header("网格设置")]
public int meshRadialSegments = 8;
public float meshTextureTiling = 1f;
#endregion #endregion
#region --- --- #region --- ---
public List<RopeNode> Nodes { get; private set; } = new List<RopeNode>(); public List<RopeNode> Nodes { get; private set; } = new List<RopeNode>();
#endregion #endregion
#region --- --- #region --- ---
private float _segmentLength; private float _segmentLength;
private float _preferredSegmentLength; private float _preferredSegmentLength;
private Collider[] _collisionBuffer = new Collider[10]; private Collider[] _collisionBuffer = new Collider[10];
private List<Collider> _ignoreColliders = new List<Collider>(); private List<Collider> _ignoreColliders = new List<Collider>();
private RopeMesh _ropeMesh; private IRopeRenderer _ropeRenderer;
#endregion #endregion
#region --- --- #region --- ---
private void OnEnable() private void OnEnable()
{ {
if(enabled) if (enabled)
VerletPhysics.RegisterRopeGenerator(this); VerletPhysics.RegisterRopeGenerator(this);
} }
private void OnDisable() private void OnDisable()
{ {
VerletPhysics.UnregisterRopeGenerator(this); VerletPhysics.UnregisterRopeGenerator(this);
} }
private void Awake()
{
ResolveRenderer();
}
private void Start() private void Start()
{ {
if(initializeOnStart && enabled) if (initializeOnStart && enabled)
InitializeRope(); InitializeRope();
} }
private void ResolveRenderer()
{
if (_ropeRenderer != null) return;
var behaviours = GetComponents<MonoBehaviour>();
foreach (var behaviour in behaviours)
{
if (behaviour is not IRopeRenderer ropeRenderer) continue;
_ropeRenderer = ropeRenderer;
break;
}
if (_ropeRenderer == null)
Debug.LogWarning("未找到 IRopeRenderer 实现,绳索将不会渲染。请在预制体上绑定渲染组件。", this);
}
/// <summary> /// <summary>
/// 根据参数从零创建绳索。 /// 根据参数从零创建绳索。
/// </summary> /// </summary>
public void InitializeRope() public void InitializeRope()
{ {
_ropeMesh?.ClearMesh(); ResolveRenderer();
_ropeMesh = new RopeMesh(GetComponent<MeshFilter>(), this, meshRadialSegments, meshTextureTiling); _ropeRenderer?.Clear();
_ropeRenderer?.Bind(this);
Nodes.Clear(); Nodes.Clear();
_ignoreColliders.Clear(); _ignoreColliders.Clear();
var startPos = ropeStartPoint; var startPos = ropeStartPoint;
var endPos = ropeEndPoint; var endPos = ropeEndPoint;
if(Vector3.Distance(startPos, endPos) < 0.01f) if (Vector3.Distance(startPos, endPos) < 0.01f)
endPos = startPos + Vector3.forward * 5f; endPos = startPos + Vector3.forward * 5f;
var totalDistance = Vector3.Distance(startPos, endPos); var totalDistance = Vector3.Distance(startPos, endPos);
var diameter = ropeRadius * 2f; var diameter = ropeRadius * 2f;
@@ -195,39 +206,39 @@ namespace AdvancedRope
var minNodes = 2; var minNodes = 2;
var nodeCount = Mathf.RoundToInt(Mathf.Lerp(minNodes, maxNodes, ropeResolution)); var nodeCount = Mathf.RoundToInt(Mathf.Lerp(minNodes, maxNodes, ropeResolution));
_segmentLength = totalDistance / (nodeCount - 1); _segmentLength = totalDistance / (nodeCount - 1);
_preferredSegmentLength = _segmentLength; _preferredSegmentLength = _segmentLength;
var direction = (endPos - startPos).normalized; var direction = (endPos - startPos).normalized;
for (var i = 0; i < nodeCount; i++) for (var i = 0; i < nodeCount; i++)
{ {
var pos = startPos + direction * (_segmentLength * i); var pos = startPos + direction * (_segmentLength * i);
Nodes.Add(new RopeNode(pos)); Nodes.Add(new RopeNode(pos));
} }
// 应用初始固定点配置 // 应用初始固定点配置
ApplyInitialPins(); ApplyInitialPins();
if(connectedBodyStart) if (connectedBodyStart)
_ignoreColliders.AddRange(connectedBodyStart.GetComponentsInChildren<Collider>()); _ignoreColliders.AddRange(connectedBodyStart.GetComponentsInChildren<Collider>());
if(connectedBodyEnd) if (connectedBodyEnd)
_ignoreColliders.AddRange(connectedBodyEnd.GetComponentsInChildren<Collider>()); _ignoreColliders.AddRange(connectedBodyEnd.GetComponentsInChildren<Collider>());
_ropeMesh.SetupRopeMesh(); _ropeRenderer?.Rebuild();
} }
private void ApplyInitialPins() private void ApplyInitialPins()
{ {
if(initialPinPoints == null) return; if (initialPinPoints == null) return;
foreach (var pin in initialPinPoints) foreach (var pin in initialPinPoints)
{ {
var index = pin.nodeIndex; var index = pin.nodeIndex;
if (index < 0) if (index < 0)
index = Nodes.Count + index; index = Nodes.Count + index;
if(index >= 0 && index < Nodes.Count && pin.targetTransform) if (index >= 0 && index < Nodes.Count && pin.targetTransform)
PinNode(index, pin.targetTransform, pin.localPos); PinNode(index, pin.targetTransform, pin.localPos);
} }
} }
@@ -238,15 +249,15 @@ namespace AdvancedRope
private void LateUpdate() private void LateUpdate()
{ {
if(!enabled) return; if (!enabled) return;
_ropeMesh.UpdateMesh(); _ropeRenderer?.Render();
} }
private void FixedUpdate() private void FixedUpdate()
{ {
if(!enabled) return; if (!enabled) return;
if(Nodes.Count < 2) return; if (Nodes.Count < 2) return;
SimulateVerlet(); SimulateVerlet();
@@ -256,9 +267,9 @@ namespace AdvancedRope
ApplyInterRopeConnection(); ApplyInterRopeConnection();
// 如需强制同步连接节点,可启用下一行 // 如需强制同步连接节点,可启用下一行
// 每次迭代都检查碰撞 // 每次迭代都检查碰撞
if(enableCollisions) if (enableCollisions)
ResolveCollisions(); ResolveCollisions();
} }
@@ -273,15 +284,15 @@ namespace AdvancedRope
for (var i = 0; i < Nodes.Count; i++) for (var i = 0; i < Nodes.Count; i++)
{ {
var myNode = Nodes[i]; var myNode = Nodes[i];
if(myNode.connectedNode == null) continue; if (myNode.connectedNode == null) continue;
var otherNode = myNode.connectedNode; var otherNode = myNode.connectedNode;
var currentVector = myNode.position - otherNode.position; var currentVector = myNode.position - otherNode.position;
var targetVector = myNode.connectionOffset; var targetVector = myNode.connectionOffset;
var diff = currentVector - targetVector; var diff = currentVector - targetVector;
var moveFactorA = 0.5f; var moveFactorA = 0.5f;
var moveFactorB = 0.5f; var moveFactorB = 0.5f;
@@ -295,14 +306,14 @@ namespace AdvancedRope
moveFactorA = 1f; moveFactorA = 1f;
moveFactorB = 0f; moveFactorB = 0f;
} }
if(moveFactorA > 0f) if (moveFactorA > 0f)
myNode.position -= diff * moveFactorA; myNode.position -= diff * moveFactorA;
if(moveFactorB > 0f) if (moveFactorB > 0f)
otherNode.position += diff * moveFactorB; otherNode.position += diff * moveFactorB;
} }
} }
/// <summary> /// <summary>
/// 在约束迭代过程中防止连接节点发生漂移。 /// 在约束迭代过程中防止连接节点发生漂移。
/// </summary> /// </summary>
@@ -313,7 +324,7 @@ namespace AdvancedRope
node.position = node.connectedNode.position + node.connectionOffset; node.position = node.connectedNode.position + node.connectionOffset;
} }
/// <summary> /// <summary>
/// 基础 Verlet 积分x(t+dt) = 2x(t) - x(t-dt) + a*dt*dt /// 基础 Verlet 积分x(t+dt) = 2x(t) - x(t-dt) + a*dt*dt
/// </summary> /// </summary>
@@ -331,12 +342,13 @@ namespace AdvancedRope
node.position = node.pinnedTransform.TransformPoint(node.pinLocalPos); node.position = node.pinnedTransform.TransformPoint(node.pinLocalPos);
node.prevPosition = node.position; node.prevPosition = node.position;
} }
continue; continue;
} }
var velocity = (node.position - node.prevPosition) * (1f - airFriction); var velocity = (node.position - node.prevPosition) * (1f - airFriction);
var newPos = node.position + velocity + gravityStep; var newPos = node.position + velocity + gravityStep;
node.prevPosition = node.position; node.prevPosition = node.position;
node.position = newPos; node.position = newPos;
} }
@@ -348,17 +360,17 @@ namespace AdvancedRope
{ {
var nodeA = Nodes[i]; var nodeA = Nodes[i];
var nodeB = Nodes[i + 1]; var nodeB = Nodes[i + 1];
var diff = nodeA.position - nodeB.position; var diff = nodeA.position - nodeB.position;
var dist = diff.magnitude; var dist = diff.magnitude;
if (dist < 0.0001f) continue; if (dist < 0.0001f) continue;
var differance = (dist - _segmentLength) / dist; var differance = (dist - _segmentLength) / dist;
var translate = diff * (0.5f * differance * stiffness); var translate = diff * (0.5f * differance * stiffness);
if (!nodeA.isPinned) if (!nodeA.isPinned)
nodeA.position -= translate; nodeA.position -= translate;
if (!nodeB.isPinned) if (!nodeB.isPinned)
nodeB.position += translate; nodeB.position += translate;
} }
@@ -373,7 +385,7 @@ namespace AdvancedRope
/// </summary> /// </summary>
private void ApplyCouplingForces() private void ApplyCouplingForces()
{ {
if(connectedBodyStart && Nodes.Count > 0) if (connectedBodyStart && Nodes.Count > 0)
{ {
var firstNode = Nodes[0]; var firstNode = Nodes[0];
@@ -388,12 +400,12 @@ namespace AdvancedRope
} }
} }
} }
if(connectedBodyEnd && Nodes.Count > 0) if (connectedBodyEnd && Nodes.Count > 0)
{ {
var lastNode = Nodes[^1]; var lastNode = Nodes[^1];
var prevNode = Nodes[^2]; var prevNode = Nodes[^2];
var pullDir = (prevNode.position - lastNode.position).normalized; var pullDir = (prevNode.position - lastNode.position).normalized;
var dist = Vector3.Distance(prevNode.position, lastNode.position); var dist = Vector3.Distance(prevNode.position, lastNode.position);
@@ -416,7 +428,7 @@ namespace AdvancedRope
{ {
for (var i = 0; i < Nodes.Count; i++) for (var i = 0; i < Nodes.Count; i++)
{ {
if(Nodes[i].isPinned) continue; if (Nodes[i].isPinned) continue;
HandleTerrainCollision(Nodes[i]); HandleTerrainCollision(Nodes[i]);
@@ -431,17 +443,18 @@ namespace AdvancedRope
{ {
var terrain = UnityEngine.Terrain.activeTerrain; var terrain = UnityEngine.Terrain.activeTerrain;
if (!terrain) return; if (!terrain) return;
var terrainHeight = terrain.SampleHeight(node.position) + terrain.transform.position.y; var terrainHeight = terrain.SampleHeight(node.position) + terrain.transform.position.y;
if (node.position.y < terrainHeight + ropeRadius) if (node.position.y < terrainHeight + ropeRadius)
{ {
node.position = new Vector3(node.position.x, terrainHeight + ropeRadius, node.position.z); node.position = new Vector3(node.position.x, terrainHeight + ropeRadius, node.position.z);
// 地面摩擦 // 地面摩擦
var vel = node.position - node.prevPosition; var vel = node.position - node.prevPosition;
node.prevPosition = node.position - (vel * 0.5f); node.prevPosition = node.position - (vel * 0.5f);
} }
} }
private void HandleStandardCollisions(RopeNode node) private void HandleStandardCollisions(RopeNode node)
{ {
var count = Physics.OverlapSphereNonAlloc(node.position, ropeRadius, _collisionBuffer, collisionLayer); var count = Physics.OverlapSphereNonAlloc(node.position, ropeRadius, _collisionBuffer, collisionLayer);
@@ -449,7 +462,7 @@ namespace AdvancedRope
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
var col = _collisionBuffer[i]; var col = _collisionBuffer[i];
if(col is TerrainCollider) continue; if (col is TerrainCollider) continue;
if (_ignoreColliders.Contains(col)) continue; if (_ignoreColliders.Contains(col)) continue;
if (col is MeshCollider { convex: false } meshCol) if (col is MeshCollider { convex: false } meshCol)
@@ -457,40 +470,40 @@ namespace AdvancedRope
HandleNonConvexCollision(node, meshCol); HandleNonConvexCollision(node, meshCol);
continue; continue;
} }
var closestPoint = col.ClosestPoint(node.position); var closestPoint = col.ClosestPoint(node.position);
var distToSurface = Vector3.Distance(node.position, closestPoint); var distToSurface = Vector3.Distance(node.position, closestPoint);
if (distToSurface < ropeRadius) if (distToSurface < ropeRadius)
{ {
var pushDir = (node.position - closestPoint).normalized; var pushDir = (node.position - closestPoint).normalized;
if(pushDir == Vector3.zero) if (pushDir == Vector3.zero)
pushDir = (node.position - node.prevPosition).normalized; pushDir = (node.position - node.prevPosition).normalized;
var penetrationDepth = ropeRadius - distToSurface; var penetrationDepth = ropeRadius - distToSurface;
var pushVector = pushDir * penetrationDepth; var pushVector = pushDir * penetrationDepth;
if(!node.isPinned) if (!node.isPinned)
node.position += pushVector; node.position += pushVector;
// 对刚体施加反作用力 // 对刚体施加反作用力
var targetRb = col.attachedRigidbody; var targetRb = col.attachedRigidbody;
if (targetRb && !targetRb.isKinematic) if (targetRb && !targetRb.isKinematic)
{ {
var scale = collisionPushPower / constraintIterations; var scale = collisionPushPower / constraintIterations;
var reactionForce = -pushDir * (penetrationDepth * scale); var reactionForce = -pushDir * (penetrationDepth * scale);
targetRb.AddForceAtPosition(reactionForce, node.position, ForceMode.Impulse); targetRb.AddForceAtPosition(reactionForce, node.position, ForceMode.Impulse);
// 摩擦效果 // 摩擦效果
var nodeVel = (node.position - node.prevPosition) / Time.fixedDeltaTime; var nodeVel = (node.position - node.prevPosition) / Time.fixedDeltaTime;
var rbVel = targetRb.GetPointVelocity(node.position); var rbVel = targetRb.GetPointVelocity(node.position);
var relativeVel = rbVel - nodeVel; var relativeVel = rbVel - nodeVel;
var tangentVel = Vector3.ProjectOnPlane(relativeVel, pushDir); var tangentVel = Vector3.ProjectOnPlane(relativeVel, pushDir);
var normalForceMag = reactionForce.magnitude; var normalForceMag = reactionForce.magnitude;
var frictionMag = normalForceMag * contactFriction; var frictionMag = normalForceMag * contactFriction;
@@ -498,7 +511,7 @@ namespace AdvancedRope
{ {
var frictionDir = -tangentVel.normalized; var frictionDir = -tangentVel.normalized;
var frictionForce = frictionDir * frictionMag; var frictionForce = frictionDir * frictionMag;
targetRb.AddForceAtPosition(frictionForce, node.position, ForceMode.Impulse); targetRb.AddForceAtPosition(frictionForce, node.position, ForceMode.Impulse);
if (!node.isPinned) if (!node.isPinned)
@@ -511,28 +524,30 @@ namespace AdvancedRope
} }
} }
} }
private void HandleNonConvexCollision(RopeNode node, MeshCollider meshCol) private void HandleNonConvexCollision(RopeNode node, MeshCollider meshCol)
{ {
var travelDir = node.position - node.prevPosition; var travelDir = node.position - node.prevPosition;
var travelDist = travelDir.magnitude; var travelDist = travelDir.magnitude;
if (travelDist < 0.0001f) return; if (travelDist < 0.0001f) return;
if (Physics.SphereCast(node.prevPosition, ropeRadius, travelDir.normalized, out var hit, if (Physics.SphereCast(node.prevPosition, ropeRadius, travelDir.normalized, out var hit,
travelDist + ropeRadius, collisionLayer)) travelDist + ropeRadius, collisionLayer))
{ {
if(hit.collider == meshCol) if (hit.collider == meshCol)
node.position = hit.point + hit.normal * (ropeRadius * 1.1f); node.position = hit.point + hit.normal * (ropeRadius * 1.1f);
} }
} }
private void HandleSelfCollision(int currentIndex) private void HandleSelfCollision(int currentIndex)
{ {
var currentNode = Nodes[currentIndex]; var currentNode = Nodes[currentIndex];
for (var i = 0; i < Nodes.Count; i++) for (var i = 0; i < Nodes.Count; i++)
{ {
if(Mathf.Abs(currentIndex - i) <= 1) continue; if (Mathf.Abs(currentIndex - i) <= 1) continue;
var otherNode = Nodes[i]; var otherNode = Nodes[i];
var distSqr = (currentNode.position - otherNode.position).sqrMagnitude; var distSqr = (currentNode.position - otherNode.position).sqrMagnitude;
var minSpacing = ropeRadius * 2f; var minSpacing = ropeRadius * 2f;
@@ -542,10 +557,10 @@ namespace AdvancedRope
var dist = Mathf.Sqrt(distSqr); var dist = Mathf.Sqrt(distSqr);
var pushDir = (currentNode.position - otherNode.position).normalized; var pushDir = (currentNode.position - otherNode.position).normalized;
var pushAmount = (minSpacing - dist) * 0.5f; var pushAmount = (minSpacing - dist) * 0.5f;
if(!currentNode.isPinned) if (!currentNode.isPinned)
currentNode.position += pushDir * pushAmount; currentNode.position += pushDir * pushAmount;
if(!otherNode.isPinned) if (!otherNode.isPinned)
otherNode.position -= pushDir * pushAmount; otherNode.position -= pushDir * pushAmount;
} }
} }
@@ -557,10 +572,10 @@ namespace AdvancedRope
public void Enable() public void Enable()
{ {
if(enabled) return; if (enabled) return;
if(gameObject.activeInHierarchy) if (gameObject.activeInHierarchy)
VerletPhysics.RegisterRopeGenerator(this); VerletPhysics.RegisterRopeGenerator(this);
InitializeRope(); InitializeRope();
enabled = true; enabled = true;
} }
@@ -568,7 +583,7 @@ namespace AdvancedRope
public void Disable() public void Disable()
{ {
VerletPhysics.UnregisterRopeGenerator(this); VerletPhysics.UnregisterRopeGenerator(this);
_ropeMesh?.ClearMesh(); _ropeRenderer?.Clear();
enabled = false; enabled = false;
} }
@@ -578,7 +593,7 @@ namespace AdvancedRope
public void IncreaseLength() public void IncreaseLength()
{ {
if (TryInsertSegmentAtStart()) if (TryInsertSegmentAtStart())
_ropeMesh?.SetupRopeMesh(); _ropeRenderer?.Rebuild();
} }
/// <summary> /// <summary>
@@ -587,7 +602,7 @@ namespace AdvancedRope
public void DecreaseLength() public void DecreaseLength()
{ {
if (TryRemoveFreeSegmentNode()) if (TryRemoveFreeSegmentNode())
_ropeMesh?.SetupRopeMesh(); _ropeRenderer?.Rebuild();
} }
/// <summary> /// <summary>
@@ -620,7 +635,8 @@ namespace AdvancedRope
targetLength = Mathf.Max(0.001f, targetLength); targetLength = Mathf.Max(0.001f, targetLength);
var preferredSegmentLength = Mathf.Max(0.001f, _preferredSegmentLength > 0f ? _preferredSegmentLength : _segmentLength); var preferredSegmentLength = Mathf.Max(0.001f,
_preferredSegmentLength > 0f ? _preferredSegmentLength : _segmentLength);
var desiredSegmentCount = Mathf.Max(1, Mathf.RoundToInt(targetLength / preferredSegmentLength)); var desiredSegmentCount = Mathf.Max(1, Mathf.RoundToInt(targetLength / preferredSegmentLength));
var topologyChanged = false; var topologyChanged = false;
@@ -642,7 +658,7 @@ namespace AdvancedRope
_segmentLength = targetLength / Mathf.Max(1, Nodes.Count - 1); _segmentLength = targetLength / Mathf.Max(1, Nodes.Count - 1);
if (topologyChanged) if (topologyChanged)
_ropeMesh?.SetupRopeMesh(); _ropeRenderer?.Rebuild();
} }
/// <summary> /// <summary>
@@ -675,7 +691,7 @@ namespace AdvancedRope
return false; return false;
} }
/// <summary> /// <summary>
/// 将指定索引节点固定到目标变换组件,可选本地偏移。 /// 将指定索引节点固定到目标变换组件,可选本地偏移。
/// </summary> /// </summary>
@@ -684,32 +700,32 @@ namespace AdvancedRope
/// <param name="localOffset"></param> /// <param name="localOffset"></param>
public void PinNode(int index, Transform target, Vector3 localOffset = default) public void PinNode(int index, Transform target, Vector3 localOffset = default)
{ {
if(index < 0) if (index < 0)
index = Nodes.Count + index; index = Nodes.Count + index;
if (index >= 0 && index < Nodes.Count) if (index >= 0 && index < Nodes.Count)
{ {
Nodes[index].PinTo(target, localOffset); Nodes[index].PinTo(target, localOffset);
if (index == 0) if (index == 0)
ropeStartPoint = target.position; ropeStartPoint = target.position;
if(index == Nodes.Count -1) if (index == Nodes.Count - 1)
ropeEndPoint = target.position; ropeEndPoint = target.position;
} }
} }
/// <summary> /// <summary>
/// 解除指定索引节点的固定。 /// 解除指定索引节点的固定。
/// </summary> /// </summary>
/// <param name="index">0 表示起点,-1 表示末尾节点</param> /// <param name="index">0 表示起点,-1 表示末尾节点</param>
public void UnpinNode(int index) public void UnpinNode(int index)
{ {
if(index < 0) if (index < 0)
index = Nodes.Count + index; index = Nodes.Count + index;
if (index >= 0 && index < Nodes.Count) if (index >= 0 && index < Nodes.Count)
{ {
Nodes[index].Unpin(); Nodes[index].Unpin();
if(index == 0) if (index == 0)
ropeStartPoint = transform.position; ropeStartPoint = transform.position;
if(index == Nodes.Count -1) if (index == Nodes.Count - 1)
ropeEndPoint = transform.position + Vector3.forward * 5f; ropeEndPoint = transform.position + Vector3.forward * 5f;
} }
} }
@@ -722,30 +738,31 @@ namespace AdvancedRope
/// <param name="time"></param> /// <param name="time"></param>
public void ConnectStartRigidbody(Rigidbody connectedRb, Vector3 localPos, float time) public void ConnectStartRigidbody(Rigidbody connectedRb, Vector3 localPos, float time)
{ {
StopCoroutine(RigibBodyConnectionRoute(connectedRb,localPos, time)); StopCoroutine(RigibBodyConnectionRoute(connectedRb, localPos, time));
if(connectedBodyStart) if (connectedBodyStart)
{ {
_ignoreColliders.RemoveAll(c => _ignoreColliders.RemoveAll(c =>
c && (c.gameObject == connectedBodyStart.gameObject) || c && (c.gameObject == connectedBodyStart.gameObject) ||
c.transform.IsChildOf(connectedBodyStart.transform)); c.transform.IsChildOf(connectedBodyStart.transform));
} }
UnpinNode(0); UnpinNode(0);
connectedBodyStart = null; connectedBodyStart = null;
if(Nodes.Count == 0) return; if (Nodes.Count == 0) return;
Nodes[0].isPinned = true; Nodes[0].isPinned = true;
StartCoroutine(RigibBodyConnectionRoute(connectedRb, localPos, time)); StartCoroutine(RigibBodyConnectionRoute(connectedRb, localPos, time));
} }
private IEnumerator RigibBodyConnectionRoute(Rigidbody connectedRb, Vector3 localPos, float time) private IEnumerator RigibBodyConnectionRoute(Rigidbody connectedRb, Vector3 localPos, float time)
{ {
var dist = Vector3.Distance(Nodes[0].position, connectedRb.position + connectedRb.transform.TransformVector(localPos)); var dist = Vector3.Distance(Nodes[0].position,
connectedRb.position + connectedRb.transform.TransformVector(localPos));
var deltaTime = Time.fixedDeltaTime; var deltaTime = Time.fixedDeltaTime;
var deltaDist = dist; var deltaDist = dist;
if(time > 0.01f) if (time > 0.01f)
deltaDist = deltaTime * (dist / time); deltaDist = deltaTime * (dist / time);
var wait = new WaitForFixedUpdate(); var wait = new WaitForFixedUpdate();
while (dist > 0.01f) while (dist > 0.01f)
@@ -757,6 +774,7 @@ namespace AdvancedRope
connectedRb.position + connectedRb.transform.TransformVector(localPos)); connectedRb.position + connectedRb.transform.TransformVector(localPos));
yield return wait; yield return wait;
} }
connectedBodyStart = connectedRb; connectedBodyStart = connectedRb;
_ignoreColliders.AddRange(connectedRb.GetComponentsInChildren<Collider>()); _ignoreColliders.AddRange(connectedRb.GetComponentsInChildren<Collider>());
PinNode(0, connectedBodyStart.transform, localPos); PinNode(0, connectedBodyStart.transform, localPos);
@@ -772,14 +790,15 @@ namespace AdvancedRope
if (connectedBodyStart) if (connectedBodyStart)
{ {
_ignoreColliders.RemoveAll(c => _ignoreColliders.RemoveAll(c =>
c&& (c.gameObject == connectedBodyStart.gameObject) || c && (c.gameObject == connectedBodyStart.gameObject) ||
c.transform.IsChildOf(connectedBodyStart.transform)); c.transform.IsChildOf(connectedBodyStart.transform));
} }
connectedBodyStart = connectedRb; connectedBodyStart = connectedRb;
_ignoreColliders.AddRange(connectedRb.GetComponentsInChildren<Collider>()); _ignoreColliders.AddRange(connectedRb.GetComponentsInChildren<Collider>());
PinNode(0, connectedBodyStart.transform, localPos); PinNode(0, connectedBodyStart.transform, localPos);
} }
/// <summary> /// <summary>
/// 将绳索末端连接到指定刚体。 /// 将绳索末端连接到指定刚体。
/// </summary> /// </summary>
@@ -790,103 +809,108 @@ namespace AdvancedRope
if (connectedBodyEnd) if (connectedBodyEnd)
{ {
_ignoreColliders.RemoveAll(c => _ignoreColliders.RemoveAll(c =>
c && (c.gameObject == connectedBodyEnd.gameObject) || c && (c.gameObject == connectedBodyEnd.gameObject) ||
c.transform.IsChildOf(connectedBodyEnd.transform)); c.transform.IsChildOf(connectedBodyEnd.transform));
} }
connectedBodyEnd = connectedRb; connectedBodyEnd = connectedRb;
_ignoreColliders.AddRange(connectedRb.GetComponentsInChildren<Collider>()); _ignoreColliders.AddRange(connectedRb.GetComponentsInChildren<Collider>());
PinNode(-1, connectedBodyEnd.transform, localPos); PinNode(-1, connectedBodyEnd.transform, localPos);
} }
/// <summary> /// <summary>
/// 断开绳索起点与刚体的连接。 /// 断开绳索起点与刚体的连接。
/// </summary> /// </summary>
public void DisconnectStartRigidbody() public void DisconnectStartRigidbody()
{ {
if (!connectedBodyStart) return; if (!connectedBodyStart) return;
_ignoreColliders.RemoveAll(c => _ignoreColliders.RemoveAll(c =>
c != null && (c.gameObject == connectedBodyStart.gameObject) || c != null && (c.gameObject == connectedBodyStart.gameObject) ||
c.transform.IsChildOf(connectedBodyStart.transform)); c.transform.IsChildOf(connectedBodyStart.transform));
connectedBodyStart = null; connectedBodyStart = null;
UnpinNode(0); UnpinNode(0);
} }
/// <summary> /// <summary>
/// 断开绳索末端与刚体的连接。 /// 断开绳索末端与刚体的连接。
/// </summary> /// </summary>
public void DisconnectEndRigidbody() public void DisconnectEndRigidbody()
{ {
if (!connectedBodyEnd) return; if (!connectedBodyEnd) return;
_ignoreColliders.RemoveAll(c => _ignoreColliders.RemoveAll(c =>
c != null && (c.gameObject == connectedBodyEnd.gameObject) || c != null && (c.gameObject == connectedBodyEnd.gameObject) ||
c.transform.IsChildOf(connectedBodyEnd.transform)); c.transform.IsChildOf(connectedBodyEnd.transform));
connectedBodyEnd = null; connectedBodyEnd = null;
UnpinNode(-1); UnpinNode(-1);
} }
/// <summary> /// <summary>
/// 将当前绳索末端连接到目标绳索的某个节点。 /// 将当前绳索末端连接到目标绳索的某个节点。
/// </summary> /// </summary>
/// <param name="targetRope"></param> /// <param name="targetRope"></param>
/// <param name="targetNodeIndex"></param> /// <param name="targetNodeIndex"></param>
/// <param name="connectionOffset"></param> /// <param name="connectionOffset"></param>
public void ConnectEndToOtherRope(VerletRopeGenerator targetRope, int targetNodeIndex, Vector3 connectionOffset = default) public void ConnectEndToOtherRope(VerletRopeGenerator targetRope, int targetNodeIndex,
Vector3 connectionOffset = default)
{ {
ConnectToOtherRope(-1, targetRope, targetNodeIndex, connectionOffset); ConnectToOtherRope(-1, targetRope, targetNodeIndex, connectionOffset);
} }
/// <summary> /// <summary>
/// 将当前绳索起点连接到目标绳索的某个节点。 /// 将当前绳索起点连接到目标绳索的某个节点。
/// </summary> /// </summary>
/// <param name="targetRope"></param> /// <param name="targetRope"></param>
/// <param name="targetNodeIndex"></param> /// <param name="targetNodeIndex"></param>
/// <param name="connectionOffset"></param> /// <param name="connectionOffset"></param>
public void ConnectStartToOtherRope(VerletRopeGenerator targetRope, int targetNodeIndex, Vector3 connectionOffset = default) public void ConnectStartToOtherRope(VerletRopeGenerator targetRope, int targetNodeIndex,
Vector3 connectionOffset = default)
{ {
ConnectToOtherRope(0, targetRope, targetNodeIndex, connectionOffset); ConnectToOtherRope(0, targetRope, targetNodeIndex, connectionOffset);
} }
/// <summary> /// <summary>
/// 将当前绳索的某个节点连接到目标绳索的某个节点。 /// 将当前绳索的某个节点连接到目标绳索的某个节点。
/// </summary> /// </summary>
/// <param name="myNodeIndex">0 表示起点,-1 表示末尾节点</param> /// <param name="myNodeIndex">0 表示起点,-1 表示末尾节点</param>
/// <param name="targetRope"></param> /// <param name="targetRope"></param>
/// <param name="targetNodeIndex">0 表示起点,-1 表示末尾节点</param> /// <param name="targetNodeIndex">0 表示起点,-1 表示末尾节点</param>
public void ConnectToOtherRope(int myNodeIndex, VerletRopeGenerator targetRope, int targetNodeIndex, Vector3 connectionOffset = default) public void ConnectToOtherRope(int myNodeIndex, VerletRopeGenerator targetRope, int targetNodeIndex,
Vector3 connectionOffset = default)
{ {
if(myNodeIndex < 0) if (myNodeIndex < 0)
myNodeIndex = Nodes.Count + myNodeIndex; myNodeIndex = Nodes.Count + myNodeIndex;
if(targetNodeIndex < 0) if (targetNodeIndex < 0)
targetNodeIndex = targetRope.Nodes.Count + targetNodeIndex; targetNodeIndex = targetRope.Nodes.Count + targetNodeIndex;
if(myNodeIndex < 0 || myNodeIndex >= Nodes.Count) return; if (myNodeIndex < 0 || myNodeIndex >= Nodes.Count) return;
if(!targetRope || targetNodeIndex < 0 || targetNodeIndex >= targetRope.Nodes.Count) return; if (!targetRope || targetNodeIndex < 0 || targetNodeIndex >= targetRope.Nodes.Count) return;
var myNode = Nodes[myNodeIndex]; var myNode = Nodes[myNodeIndex];
var targetNode = targetRope.Nodes[targetNodeIndex]; var targetNode = targetRope.Nodes[targetNodeIndex];
if(targetNodeIndex == 0) if (targetNodeIndex == 0)
targetRope.DisconnectStartRigidbody(); targetRope.DisconnectStartRigidbody();
if(targetNodeIndex == targetRope.Nodes.Count - 1) if (targetNodeIndex == targetRope.Nodes.Count - 1)
targetRope.DisconnectEndRigidbody(); targetRope.DisconnectEndRigidbody();
myNode.ConnectToNode(targetNode, connectionOffset); myNode.ConnectToNode(targetNode, connectionOffset);
targetNode.ConnectToNode(myNode, -connectionOffset); targetNode.ConnectToNode(myNode, -connectionOffset);
} }
/// <summary> /// <summary>
/// 断开该节点与其他绳索节点的连接。 /// 断开该节点与其他绳索节点的连接。
/// </summary> /// </summary>
/// <param name="myNodeIndex">0 表示起点,-1 表示末尾节点</param> /// <param name="myNodeIndex">0 表示起点,-1 表示末尾节点</param>
public void DisconnectFromOtherRope(int myNodeIndex) public void DisconnectFromOtherRope(int myNodeIndex)
{ {
if(myNodeIndex < 0) if (myNodeIndex < 0)
myNodeIndex = Nodes.Count + myNodeIndex; myNodeIndex = Nodes.Count + myNodeIndex;
if(myNodeIndex < 0 || myNodeIndex >= Nodes.Count) return; if (myNodeIndex < 0 || myNodeIndex >= Nodes.Count) return;
var myNode = Nodes[myNodeIndex]; var myNode = Nodes[myNodeIndex];
var targetNode = myNode.connectedNode; var targetNode = myNode.connectedNode;
myNode.ConnectToNode(null); myNode.ConnectToNode(null);
@@ -910,7 +934,7 @@ namespace AdvancedRope
{ {
var p1 = Nodes[i].position; var p1 = Nodes[i].position;
var p2 = Nodes[i + 1].position; var p2 = Nodes[i + 1].position;
var distSqr = SqrDistanceRaySegment(ray, p1, p2, out var rayParam, out var segmentParam); var distSqr = SqrDistanceRaySegment(ray, p1, p2, out var rayParam, out var segmentParam);
if (distSqr <= (ropeRadius * ropeRadius) && rayParam < maxDistance && rayParam > 0) if (distSqr <= (ropeRadius * ropeRadius) && rayParam < maxDistance && rayParam > 0)
@@ -918,7 +942,7 @@ namespace AdvancedRope
if (rayParam < closestDistSqr) if (rayParam < closestDistSqr)
{ {
closestDistSqr = rayParam; closestDistSqr = rayParam;
hitInfo.DidHit = true; hitInfo.DidHit = true;
hitInfo.Rope = this; hitInfo.Rope = this;
hitInfo.NodeIndex = segmentParam < 0.5f ? i : i + 1; hitInfo.NodeIndex = segmentParam < 0.5f ? i : i + 1;
@@ -926,27 +950,28 @@ namespace AdvancedRope
hitInfo.Distance = rayParam; hitInfo.Distance = rayParam;
hitInfo.SegmentRatio = segmentParam; hitInfo.SegmentRatio = segmentParam;
hitInfo.Point = ray.GetPoint(rayParam); hitInfo.Point = ray.GetPoint(rayParam);
hasHit = true; hasHit = true;
} }
} }
} }
return hasHit; return hasHit;
} }
private static float SqrDistanceRaySegment(Ray ray, Vector3 s1, Vector3 s2, out float rayParam, out float segmentParam) private static float SqrDistanceRaySegment(Ray ray, Vector3 s1, Vector3 s2, out float rayParam,
out float segmentParam)
{ {
var u = ray.direction; var u = ray.direction;
var v = s2 - s1; var v = s2 - s1;
var w = ray.origin - s1; var w = ray.origin - s1;
var a = Vector3.Dot(u, u); var a = Vector3.Dot(u, u);
var b = Vector3.Dot(u, v); var b = Vector3.Dot(u, v);
var c = Vector3.Dot(v, v); var c = Vector3.Dot(v, v);
var d = Vector3.Dot(u, w); var d = Vector3.Dot(u, w);
var e = Vector3.Dot(v, w); var e = Vector3.Dot(v, w);
var denom = a * c - b * b; var denom = a * c - b * b;
float sc, tc; float sc, tc;
@@ -960,43 +985,44 @@ namespace AdvancedRope
sc = (b * e - c * d) / denom; sc = (b * e - c * d) / denom;
tc = (a * e - b * d) / denom; tc = (a * e - b * d) / denom;
} }
rayParam = sc; rayParam = sc;
segmentParam = Mathf.Clamp01(tc); segmentParam = Mathf.Clamp01(tc);
var closestPointOnSegment = s1 + v * segmentParam; var closestPointOnSegment = s1 + v * segmentParam;
var delta = closestPointOnSegment - ray.origin; var delta = closestPointOnSegment - ray.origin;
rayParam = Vector3.Dot(delta, ray.direction); rayParam = Vector3.Dot(delta, ray.direction);
if(rayParam < 0f) if (rayParam < 0f)
rayParam = 0f; rayParam = 0f;
var closestPointOnRay = ray.GetPoint(rayParam); var closestPointOnRay = ray.GetPoint(rayParam);
return (closestPointOnSegment - closestPointOnRay).sqrMagnitude; return (closestPointOnSegment - closestPointOnRay).sqrMagnitude;
} }
#endregion #endregion
#region --- / --- #region --- / ---
private void OnDrawGizmos() private void OnDrawGizmos()
{ {
if(Nodes == null || Nodes.Count == 0) return; if (Nodes == null || Nodes.Count == 0) return;
Gizmos.color = Color.green; Gizmos.color = Color.green;
for (var i = 0; i < Nodes.Count - 1; i++) for (var i = 0; i < Nodes.Count - 1; i++)
Gizmos.DrawLine(Nodes[i].position, Nodes[i + 1].position); Gizmos.DrawLine(Nodes[i].position, Nodes[i + 1].position);
Gizmos.color = Color.red; Gizmos.color = Color.red;
foreach (var node in Nodes) foreach (var node in Nodes)
if(node.isPinned) if (node.isPinned)
Gizmos.DrawWireSphere(node.position, ropeRadius * 1.2f); Gizmos.DrawWireSphere(node.position, ropeRadius * 1.2f);
} }
private void OnDrawGizmosSelected() private void OnDrawGizmosSelected()
{ {
if(Application.isPlaying) return; if (Application.isPlaying) return;
var startPos = ropeStartPoint; var startPos = ropeStartPoint;
var endPos = ropeEndPoint; var endPos = ropeEndPoint;
@@ -1025,7 +1051,7 @@ namespace AdvancedRope
foreach (var pin in initialPinPoints) foreach (var pin in initialPinPoints)
{ {
if (pin.targetTransform == null) continue; if (pin.targetTransform == null) continue;
var idx = pin.nodeIndex; var idx = pin.nodeIndex;
if (idx < 0) idx = nodeCount + idx; if (idx < 0) idx = nodeCount + idx;
@@ -1067,7 +1093,7 @@ namespace AdvancedRope
} }
} }
} }
#endregion #endregion
} }
@@ -1081,4 +1107,4 @@ namespace AdvancedRope
public float Distance; public float Distance;
public float SegmentRatio; public float SegmentRatio;
} }
} }

View File

@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff713e1c3c5f545ce9b1df47230f4f85346ae00_003Fc9_003Ff4df80af_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

View File

@@ -24,20 +24,20 @@ EditorUserSettings:
value: 055550005c055e0e58085a2741720f4415154a7f7b2c22652e794461b7e5376a value: 055550005c055e0e58085a2741720f4415154a7f7b2c22652e794461b7e5376a
flags: 0 flags: 0
RecentlyUsedSceneGuid-4: RecentlyUsedSceneGuid-4:
value: 00040c5204025e5d0f59547242710744454e4d7f28717363757c1f63e7b0633c
flags: 0
RecentlyUsedSceneGuid-5:
value: 0050525253025d0b5a0f542748710f4446151a727d2b74652e714862b4b6636d value: 0050525253025d0b5a0f542748710f4446151a727d2b74652e714862b4b6636d
flags: 0 flags: 0
RecentlyUsedSceneGuid-6: RecentlyUsedSceneGuid-5:
value: 0206055103530a0908560e21497b0d4410161c2b2a712468757e4461b3e3306a value: 0206055103530a0908560e21497b0d4410161c2b2a712468757e4461b3e3306a
flags: 0 flags: 0
RecentlyUsedSceneGuid-7: RecentlyUsedSceneGuid-6:
value: 5100040304005e5a5b0d5875482659441715412b757b74342b781b6ab7e1653b value: 5100040304005e5a5b0d5875482659441715412b757b74342b781b6ab7e1653b
flags: 0 flags: 0
RecentlyUsedSceneGuid-8: RecentlyUsedSceneGuid-7:
value: 5005025453535f0f595d597b12225d44174f1a73757f22357f711c30b4e4666a value: 5005025453535f0f595d597b12225d44174f1a73757f22357f711c30b4e4666a
flags: 0 flags: 0
RecentlyUsedSceneGuid-8:
value: 00040c5204025e5d0f59547242710744454e4d7f28717363757c1f63e7b0633c
flags: 0
vcSharedLogLevel: vcSharedLogLevel:
value: 0d5e400f0650 value: 0d5e400f0650
flags: 0 flags: 0