From 4059c207c0dc6f30bb2aefa456f23ea2865fbd0a Mon Sep 17 00:00:00 2001 From: "Bob.Song" Date: Wed, 5 Nov 2025 18:00:02 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=B3=E5=AD=90=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ThirdParty/Rope/Core/PointsExtensions.cs | 19 +- .../Rope/Examples/00_Main/Main 1.unity | 93 ++- .../Rope/Examples/00_Main/RopeLenghtTest.cs | 25 + .../Examples/00_Main/RopeLenghtTest.cs.meta | 3 + Assets/Scripts/ThirdParty/Rope/Rope.Editor.cs | 62 ++ .../ThirdParty/Rope/Rope.Editor.cs.meta | 3 + Assets/Scripts/ThirdParty/Rope/Rope.cs | 565 +++++------------- Assets/Scripts/ThirdParty/Rope/Rope2.cs | 2 +- Fishing2.sln.DotSettings.user | 3 + 9 files changed, 309 insertions(+), 466 deletions(-) create mode 100644 Assets/Scripts/ThirdParty/Rope/Examples/00_Main/RopeLenghtTest.cs create mode 100644 Assets/Scripts/ThirdParty/Rope/Examples/00_Main/RopeLenghtTest.cs.meta create mode 100644 Assets/Scripts/ThirdParty/Rope/Rope.Editor.cs create mode 100644 Assets/Scripts/ThirdParty/Rope/Rope.Editor.cs.meta diff --git a/Assets/Scripts/ThirdParty/Rope/Core/PointsExtensions.cs b/Assets/Scripts/ThirdParty/Rope/Core/PointsExtensions.cs index dedc400d7..8ff3897de 100644 --- a/Assets/Scripts/ThirdParty/Rope/Core/PointsExtensions.cs +++ b/Assets/Scripts/ThirdParty/Rope/Core/PointsExtensions.cs @@ -9,7 +9,7 @@ namespace NBF public static class PointsExtensions { // Curve length - public static float GetLengthOfCurve(this NativeArray curve, ref float4x4 transform, bool isLoop = false) + public static float GetLengthOfCurve(this NativeArray curve, ref float4x4 transform) { if (curve == null || curve.Length == 0) { @@ -24,31 +24,28 @@ namespace NBF sum += math.distance(lastPoint, point); lastPoint = point; } - if (isLoop) - { - sum += math.distance(lastPoint, firstPoint); - } + return sum; } - public static float GetLengthOfCurve(this NativeArray curve, bool isLoop = false) + public static float GetLengthOfCurve(this NativeArray curve) { var transform = float4x4.identity; - return curve.GetLengthOfCurve(ref transform, isLoop); + return curve.GetLengthOfCurve(ref transform); } - public static float GetLengthOfCurve(this IEnumerable curve, ref float4x4 transform, bool isLoop = false) + public static float GetLengthOfCurve(this IEnumerable curve, ref float4x4 transform) { var array = new NativeArray(curve.ToArray(), Allocator.Temp); - var sum = array.GetLengthOfCurve(ref transform, isLoop); + var sum = array.GetLengthOfCurve(ref transform); array.Dispose(); return sum; } - public static float GetLengthOfCurve(this IEnumerable curve, bool isLoop = false) + public static float GetLengthOfCurve(this IEnumerable curve) { var transform = float4x4.identity; - return curve.GetLengthOfCurve(ref transform, isLoop); + return curve.GetLengthOfCurve(ref transform); } // Curve points diff --git a/Assets/Scripts/ThirdParty/Rope/Examples/00_Main/Main 1.unity b/Assets/Scripts/ThirdParty/Rope/Examples/00_Main/Main 1.unity index 00f2ecdc1..253fd8cf0 100644 --- a/Assets/Scripts/ThirdParty/Rope/Examples/00_Main/Main 1.unity +++ b/Assets/Scripts/ThirdParty/Rope/Examples/00_Main/Main 1.unity @@ -252,7 +252,7 @@ Transform: m_GameObject: {fileID: 562189915} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0.7071067, w: 0.70710695} - m_LocalPosition: {x: -1.406, y: -0, z: 0.9375} + m_LocalPosition: {x: -3.47, y: -0, z: 0.9375} m_LocalScale: {x: 1, y: 0.20000012, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -394,6 +394,7 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 906432239} + - component: {fileID: 906432240} m_Layer: 0 m_Name: Swings m_TagString: Untagged @@ -418,6 +419,20 @@ Transform: - {fileID: 562189922} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &906432240 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 906432238} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2b777331b0af4d349e55170b5dc27c78, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::NBF.RopeLenghtTest + Rope: {fileID: 1197291658} + AddValue: 0.01 --- !u!1 &963194225 GameObject: m_ObjectHideFlags: 0 @@ -430,6 +445,7 @@ GameObject: - component: {fileID: 963194227} - component: {fileID: 963194229} - component: {fileID: 963194226} + - component: {fileID: 963194230} m_Layer: 0 m_Name: Camera m_TagString: MainCamera @@ -536,13 +552,51 @@ MonoBehaviour: leverage: 10 splitPickedRopeOnKey: 32 ropes: - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - {fileID: 1197291658} - - {fileID: 0} +--- !u!114 &963194230 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 + m_Version: 2 --- !u!1 &1197291657 GameObject: m_ObjectHideFlags: 0 @@ -574,14 +628,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 325b217b839086b4ca705834516bc0d5, type: 3} m_Name: m_EditorClassIdentifier: - radius: 0.05 - radialVertices: 6 - isLoop: 0 + radius: 0.01 material: {fileID: 2100000, guid: 189732c736fdb544f98524f96c34aff6, type: 2} shadowMode: 1 - rendering: - useSimpleLineRenderer: 1 - simpleLineWidth: 0.02 spawnPoints: - x: 0 y: 0 @@ -589,27 +638,25 @@ MonoBehaviour: - x: -2.4452248 y: 0.06797419 z: 0.039803684 + - x: -3.4447064 + y: 0.09575853 + z: 0.056073375 + - x: -4.444188 + y: 0.12354286 + z: 0.07234307 interpolation: 0 simulation: enabled: 1 - resolution: 5 - massPerMeter: 2 + resolution: 2 + massPerMeter: 0.02 stiffness: 1 energyLoss: 0.005 - lengthMultiplier: 1 - gravityMultiplier: 1 - useCustomGravity: 0 - customGravity: - x: 0 - y: -9.81 - z: 0 substeps: 2 solverIterations: 3 collisions: enabled: 0 influenceRigidbodies: 0 stride: 2 - friction: 0.1 collisionMargin: 0.025 ignoreLayers: serializedVersion: 2 diff --git a/Assets/Scripts/ThirdParty/Rope/Examples/00_Main/RopeLenghtTest.cs b/Assets/Scripts/ThirdParty/Rope/Examples/00_Main/RopeLenghtTest.cs new file mode 100644 index 000000000..9796617b6 --- /dev/null +++ b/Assets/Scripts/ThirdParty/Rope/Examples/00_Main/RopeLenghtTest.cs @@ -0,0 +1,25 @@ +using System; +using UnityEngine; + +namespace NBF +{ + public class RopeLenghtTest : MonoBehaviour + { + public Rope Rope; + public float AddValue = 0.01f; + private void Update() + { + if (Input.GetKeyDown(KeyCode.A)) + { + // Rope.Retract(AddValue); + Rope.PopSpawnPoint(); + } + + if (Input.GetKeyDown(KeyCode.D)) + { + // Rope.Extend(AddValue); + Rope.PushSpawnPoint(); + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/ThirdParty/Rope/Examples/00_Main/RopeLenghtTest.cs.meta b/Assets/Scripts/ThirdParty/Rope/Examples/00_Main/RopeLenghtTest.cs.meta new file mode 100644 index 000000000..d8b90cafb --- /dev/null +++ b/Assets/Scripts/ThirdParty/Rope/Examples/00_Main/RopeLenghtTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2b777331b0af4d349e55170b5dc27c78 +timeCreated: 1762329894 \ No newline at end of file diff --git a/Assets/Scripts/ThirdParty/Rope/Rope.Editor.cs b/Assets/Scripts/ThirdParty/Rope/Rope.Editor.cs new file mode 100644 index 000000000..9b52ec4bc --- /dev/null +++ b/Assets/Scripts/ThirdParty/Rope/Rope.Editor.cs @@ -0,0 +1,62 @@ +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine; + +namespace NBF +{ + public partial class Rope + { +#if UNITY_EDITOR + public struct EditorColors + { + public Color ropeSegments; + public Color simulationParticle; + public Color collisionParticle; + public Color spawnPointHandle; + } + + public static readonly EditorColors Colors = new EditorColors() + { + ropeSegments = Color.black, + simulationParticle = new Color(0.2f, 0.8f, 0.2f, 0.5f), + collisionParticle = new Color(1.0f, 0.92f, 0.016f, 0.5f), + spawnPointHandle = new Color(0.1f, 0.5f, 0.8f), + }; + + public void OnDrawGizmos() + { + if (Application.isPlaying || spawnPoints.Count < 2 || !enabled) + { + return; + } + + ComputeRealCurve(Allocator.Temp, out Measurements measurements, out NativeArray points); + + Gizmos.color = Colors.ropeSegments; + for (int i = 0; i < points.Length - 1; i++) + { + Gizmos.DrawLine(points[i], points[i + 1]); + } + + if (UnityEditor.Selection.Contains(gameObject)) + { + for (int i = 0; i < points.Length; i++) + { + if (collisions.enabled && i % collisions.stride == 0) + { + Gizmos.color = Colors.collisionParticle; + } + else + { + Gizmos.color = Colors.simulationParticle; + } + + Gizmos.DrawSphere(points[i], radius); + } + } + + points.Dispose(); + } +#endif + } +} \ No newline at end of file diff --git a/Assets/Scripts/ThirdParty/Rope/Rope.Editor.cs.meta b/Assets/Scripts/ThirdParty/Rope/Rope.Editor.cs.meta new file mode 100644 index 000000000..5f3dca24e --- /dev/null +++ b/Assets/Scripts/ThirdParty/Rope/Rope.Editor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c6bf255fcd0641f4b1b400ab03fc1496 +timeCreated: 1762332174 \ No newline at end of file diff --git a/Assets/Scripts/ThirdParty/Rope/Rope.cs b/Assets/Scripts/ThirdParty/Rope/Rope.cs index dc86f1c61..7141ee07e 100644 --- a/Assets/Scripts/ThirdParty/Rope/Rope.cs +++ b/Assets/Scripts/ThirdParty/Rope/Rope.cs @@ -16,7 +16,7 @@ namespace NBF Extrapolate, } - public class Rope : MonoBehaviour + public partial class Rope : MonoBehaviour { protected const int MaxCollisionPlanesPerParticle = 3; protected const int InitialParticleTargets = 3; @@ -35,55 +35,15 @@ namespace NBF return math.clamp((int)(distance / particleSpacing + 0.5f), 0, particleCount - 1); } } - - public struct EditorColors - { - public Color ropeSegments; - public Color simulationParticle; - public Color collisionParticle; - public Color spawnPointHandle; - } - - public static readonly EditorColors Colors = new EditorColors() - { - ropeSegments = Color.black, - simulationParticle = new Color(0.2f, 0.8f, 0.2f, 0.5f), - collisionParticle = new Color(1.0f, 0.92f, 0.016f, 0.5f), - spawnPointHandle = new Color(0.1f, 0.5f, 0.8f), - }; - - [Tooltip("绳子的半径。该值既用于构建视觉网格,也用于处理碰撞。")] [Range(0.001f, 1.0f)] + + [Tooltip("绳子的半径。该值既用于构建视觉网格,也用于处理碰撞。")] [Range(0.0001f, 1.0f)] public float radius = 0.05f; - - [Tooltip("绳子的视觉网格中每个段使用的顶点数。更多的顶点会使绳子看起来更圆,但会增加视觉网格的总体顶点和三角形数量。该值不会影响绳子的模拟。")] - [DisableInPlayMode] - [Range(3, 32)] - public int radialVertices = 6; - - [Tooltip("绳子是否为圆形环。如果启用,绳子的最后一个生成点将连接到第一个生成点。")] [DisableInPlayMode] - public bool isLoop = false; - + [Tooltip("用于渲染绳子的材质。这可以是任何使用顶点位置和可选法线的材质。")] public Material material; [Tooltip("绳子使用的阴影投射模式")] public ShadowCastingMode shadowMode = ShadowCastingMode.On; - [System.Serializable] - public struct RenderingSettings - { - [Tooltip("使用简单线渲染而非完整网格渲染。适用于细绳如鱼线,性能更好。")] - public bool useSimpleLineRenderer; - - [Tooltip("简单线渲染的宽度。")] public float simpleLineWidth; - } - - [Space] public RenderingSettings rendering = new RenderingSettings() - { - useSimpleLineRenderer = false, - simpleLineWidth = 0.02f - }; - - [Tooltip("用于初始放置绳子在世界中的生成点。目前,连续的生成点对被视为线性线段。")] [DisableInPlayMode] public List spawnPoints = new List(); @@ -108,16 +68,6 @@ namespace NBF [Tooltip("每次固定更新时从模拟中移除的能量百分比。用于模拟空气阻力。不影响性能。")] [Range(0.0f, 1.0f)] public float energyLoss; - [Header("修改器")] [Tooltip("一个动态缩短或延长绳子的乘法因子。例如,这可以用于创建可伸缩的抓钩。")] [Range(0.0f, 2.0f)] - public float lengthMultiplier; - - [Tooltip("应用于绳子的重力百分比。较低的重力倍增器可能有助于拉直否则会下垂的绳子,但应被视为'hack',因为绳子会表现得像在太空中一样。")] [Range(0.0f, 1.0f)] - public float gravityMultiplier; - - [Tooltip("是否使用此组件的自定义重力值或全局物理重力")] public bool useCustomGravity; - - [Tooltip("不使用全局重力时,此特定绳子使用的重力")] public float3 customGravity; - [Header("高级(更改这些将需要调整基本特性)")] [Range(1, 10)] [Tooltip("每个固定更新应分割成的子步数。高子步数会产生更硬的模拟,因为重力引起的微小偏转可以及早被抵消。例外情况是如果绳子在两个刚体之间固定,则项目的固定更新率决定刚度。")] @@ -133,11 +83,7 @@ namespace NBF resolution = 10.0f, massPerMeter = 0.2f, stiffness = 1.0f, - lengthMultiplier = 1.0f, energyLoss = 0.0025f, - gravityMultiplier = 1.0f, - useCustomGravity = false, - customGravity = Physics.gravity, substeps = 4, solverIterations = 2, }; @@ -151,14 +97,10 @@ namespace NBF [Tooltip("绳子碰撞时是否应影响刚体。")] public bool influenceRigidbodies; - [Tooltip( - "检查并响应每个第n个模拟粒子的碰撞。值为1将使每个模拟粒子都对碰撞做出反应,值为2将使每隔一个粒子对碰撞做出反应,以此类推。由于每个粒子执行一次球体重叠测试,低值非常消耗性能。当绳子被选中时,碰撞粒子由黄色球体可视化。")] + [Tooltip("检查并响应每个第n个模拟粒子的碰撞。值为1将使每个模拟粒子都对碰撞做出反应,值为2将使每隔一个粒子对碰撞做出反应,以此类推。由于每个粒子执行一次球体重叠测试,低值非常消耗性能。当绳子被选中时,碰撞粒子由黄色球体可视化。")] [Range(1, 20)] public int stride; - - [Tooltip("绳子的动态摩擦系数。用于在绳子被拖拽时减慢绳子的速度。")] [Range(0.0f, 20.0f)] - public float friction; - + [Tooltip("防止小半径绳子容易穿过几何体的额外距离(添加到绳子半径之上)")] [Range(0.0f, 1.0f)] public float collisionMargin; @@ -170,7 +112,6 @@ namespace NBF enabled = false, influenceRigidbodies = true, stride = 2, - friction = 0.1f, collisionMargin = 0.025f, ignoreLayers = 0, }; @@ -225,14 +166,6 @@ namespace NBF protected NativeArray particleTargets; protected NativeArray particleTargetFeedbacks; - // Rendering - protected NativeArray vertices; - protected NativeArray normals; - protected NativeArray cosLookup; - protected NativeArray sinLookup; - - protected Mesh mesh; - protected Measurements _measurements; /// @@ -250,28 +183,11 @@ namespace NBF return _measurements; } } - - /// - /// 视觉网格的当前世界空间边界 - /// - public Bounds currentBounds - { - get - { - if (!Initialize()) - { - return new Bounds(); - } - - return mesh.bounds; - } - } - + public void OnValidate() { simulation.resolution = Mathf.Max(0.01f, simulation.resolution); simulation.massPerMeter = Mathf.Max(0.01f, simulation.massPerMeter); - rendering.simpleLineWidth = Mathf.Max(0.001f, rendering.simpleLineWidth); } /// @@ -588,35 +504,10 @@ namespace NBF CompletePreviousSimulationFrame(); - return positions.GetLengthOfCurve(isLoop); + return positions.GetLengthOfCurve(); } - /// - /// 计算真实曲线 - /// - protected void ComputeRealCurve(Allocator allocator, out Measurements measurements, - out NativeArray points) - { - var localToWorld = (float4x4)transform.localToWorldMatrix; - - var spawnCurveLength = spawnPoints.GetLengthOfCurve(ref localToWorld); - var segmentCount = math.max(1, (int)(spawnCurveLength * simulation.resolution)); - var particleCount = segmentCount + 1; - var particleSpacing = spawnCurveLength / segmentCount; - - points = new NativeArray(particleCount, allocator); - spawnPoints.GetPointsAlongCurve(ref localToWorld, particleSpacing, points); - var realCurveLength = points.GetLengthOfCurve(ref localToWorld); - - measurements = new Measurements() - { - spawnCurveLength = spawnCurveLength, - realCurveLength = realCurveLength, - segmentCount = segmentCount, - particleCount = particleCount, - particleSpacing = particleSpacing, - }; - } + #region 生命周期 public void OnEnable() { @@ -633,6 +524,61 @@ namespace NBF Initialize(); } + public void FixedUpdate() + { + timeSinceFixedUpdate = 0.0f; + + if (!initialized) + { + return; + } + + if (!simulation.enabled) + { + simulationDisabledPrevFrame = true; + return; + } + + CompletePreviousSimulationFrame(); // 固定更新可能在每个渲染帧中运行多次 + + if (simulationDisabledPrevFrame) + { + queuedRigidbodyConnections.Clear(); + liveRigidbodyConnections.Clear(); + } + + simulationDisabledPrevFrame = false; + + transform.position = positions[0]; + + ApplyRigidbodyFeedback(); // 来自前一帧 + + UpdateCollisionPlanes(); + + PrepareRigidbodyConnections(); + + ScheduleNextSimulationFrame(); + } + + public void LateUpdate() + { + timeSinceFixedUpdate += Time.deltaTime; + + if (!initialized) + { + return; + } + + if (interpolation != RopeInterpolation.None) + { + ScheduleInterpolation(); + } + + CompletePreviousSimulationFrame(); + + SubmitToRenderer(); + } + public void OnDisable() { if (!initialized) @@ -673,16 +619,11 @@ namespace NBF // 刚体连接 particleTargets.Dispose(); particleTargetFeedbacks.Dispose(); - - // 渲染 - vertices.Dispose(); - normals.Dispose(); - cosLookup.Dispose(); - sinLookup.Dispose(); - - Destroy(mesh); } + + #endregion + protected bool Initialize() { if (initialized) @@ -696,23 +637,21 @@ namespace NBF return false; } - if (rendering.useSimpleLineRenderer) - { - lineRenderer = gameObject.GetComponent(); - if (lineRenderer == null) - { - lineRenderer = gameObject.AddComponent(); - } - lineRenderer.useWorldSpace = true; - lineRenderer.loop = isLoop; - lineRenderer.widthMultiplier = rendering.simpleLineWidth; - lineRenderer.material = material; - lineRenderer.shadowCastingMode = shadowMode; - lineRenderer.receiveShadows = false; - lineRenderer.alignment = LineAlignment.View; + lineRenderer = gameObject.GetComponent(); + if (lineRenderer == null) + { + lineRenderer = gameObject.AddComponent(); } + lineRenderer.useWorldSpace = true; + lineRenderer.widthMultiplier = radius; + lineRenderer.material = material; + lineRenderer.shadowCastingMode = shadowMode; + lineRenderer.receiveShadows = false; + lineRenderer.alignment = LineAlignment.View; + + // 状态 ComputeRealCurve(Allocator.Persistent, out _measurements, out positions); @@ -743,10 +682,7 @@ namespace NBF up = math.cross(tangent, bitangent); } - if (!isLoop) - { - bitangents[bitangents.Length - 1] = bitangents[bitangents.Length - 2]; - } + bitangents[bitangents.Length - 1] = bitangents[bitangents.Length - 2]; } massMultipliers = new NativeArray(_measurements.particleCount, Allocator.Persistent); @@ -768,96 +704,39 @@ namespace NBF particleTargets = new NativeArray(InitialParticleTargets, Allocator.Persistent); particleTargetFeedbacks = new NativeArray(InitialParticleTargets, Allocator.Persistent); - // 渲染 - vertices = new NativeArray(_measurements.particleCount * radialVertices, Allocator.Persistent); - normals = new NativeArray(vertices.Length, Allocator.Persistent); - cosLookup = new NativeArray(radialVertices, Allocator.Persistent); - sinLookup = new NativeArray(radialVertices, Allocator.Persistent); - - for (int i = 0; i < radialVertices; i++) - { - var angle = ((float)i / (radialVertices - 1)) * Mathf.PI * 2.0f; - cosLookup[i] = Mathf.Cos(angle); - sinLookup[i] = Mathf.Sin(angle); - } - - // 注意,网格创建后三角形和uv保持不变 - var triangleParticleMax = isLoop ? _measurements.particleCount : _measurements.particleCount - 1; - var radialTriangleCount = triangleParticleMax * (radialVertices - 1) * 2 * 3; - var capTriangleCount = isLoop ? 0 : 2 * (radialVertices - 3) * 3; - var triangleCount = radialTriangleCount + capTriangleCount; - var triangles = new int[triangleCount]; - - int idx = 0; - for (int i = 0; i < triangleParticleMax; i++) - { - int vertexOffset0 = i * radialVertices; - int vertexOffset1 = ((i + 1) % _measurements.particleCount) * radialVertices; - - for (int j = 0; j < radialVertices - 1; j++) - { - int v0 = vertexOffset0 + j + 0; - int v1 = vertexOffset0 + j + 1; - int v2 = vertexOffset1 + j + 0; - int v3 = vertexOffset1 + j + 1; - - triangles[idx++] = v0; - triangles[idx++] = v1; - triangles[idx++] = v2; - triangles[idx++] = v2; - triangles[idx++] = v1; - triangles[idx++] = v3; - } - } - - if (!isLoop) - { - for (int i = 1; i < radialVertices - 2; i++) - { - triangles[idx++] = 0; - triangles[idx++] = i + 1; - triangles[idx++] = i; - } - - int vertexOffset = triangleParticleMax * radialVertices; - for (int i = 1; i < radialVertices - 2; i++) - { - triangles[idx++] = vertexOffset; - triangles[idx++] = vertexOffset + i; - triangles[idx++] = vertexOffset + i + 1; - } - } - - var uvs = new Vector2[vertices.Length]; - for (int i = 0; i < _measurements.particleCount; i++) - { - var uv = new Vector2 - { - x = ((float)i / (_measurements.particleCount - 1)) * _measurements.realCurveLength, - }; - - for (int j = 0; j < radialVertices; j++) - { - uv.y = (float)j / (radialVertices - 1); - uvs[i * radialVertices + j] = uv; - } - } - - mesh = new Mesh - { - name = gameObject.name + "_rope" - }; - mesh.MarkDynamic(); - mesh.SetVertices(vertices); - mesh.SetNormals(normals); - mesh.uv = uvs; - mesh.triangles = triangles; - initialized = true; computingSimulationFrame = false; return true; } + /// + /// 计算真实曲线 + /// + protected void ComputeRealCurve(Allocator allocator, out Measurements measurements, + out NativeArray points) + { + var localToWorld = (float4x4)transform.localToWorldMatrix; + + var spawnCurveLength = spawnPoints.GetLengthOfCurve(ref localToWorld); + var segmentCount = math.max(1, (int)(spawnCurveLength * simulation.resolution)); + var particleCount = segmentCount + 1; + var particleSpacing = spawnCurveLength / segmentCount; + + points = new NativeArray(particleCount, allocator); + spawnPoints.GetPointsAlongCurve(ref localToWorld, particleSpacing, points); + var realCurveLength = points.GetLengthOfCurve(ref localToWorld); + + measurements = new Measurements() + { + spawnCurveLength = spawnCurveLength, + realCurveLength = realCurveLength, + segmentCount = segmentCount, + particleCount = particleCount, + particleSpacing = particleSpacing, + }; + } + + protected Collider[] collisionQueryBuffer = new Collider[MaxCollisionPlanesPerParticle]; /// @@ -1100,27 +979,25 @@ namespace NBF computingSimulationFrame = true; - var gravity = simulation.useCustomGravity ? simulation.customGravity : (float3)Physics.gravity; + var gravity = (float3)Physics.gravity; var simulate = new SimulateJob() { deltaTime = Time.fixedDeltaTime, - externalAcceleration = gravity * simulation.gravityMultiplier, + externalAcceleration = gravity, energyKept = 1.0f - simulation.energyLoss, positions = positions, prevPositions = prevPositions, massMultipliers = massMultipliers, - isLoop = isLoop, substeps = simulation.substeps, solverIterations = simulation.solverIterations, stiffness = simulation.stiffness, - desiredSpacing = _measurements.particleSpacing * simulation.lengthMultiplier, + desiredSpacing = _measurements.particleSpacing, collisionsEnabled = collisions.enabled, radius = radius + collisions.collisionMargin, - friction = collisions.friction, maxCollisionPlanesPerParticle = MaxCollisionPlanesPerParticle, collisionPlanesActive = collisionPlanesActive, collisionPlanes = collisionPlanes, @@ -1135,13 +1012,6 @@ namespace NBF { positions = positions, bitangents = bitangents, - isLoop = isLoop, - radialVertices = radialVertices, - radius = radius, - cosLookup = cosLookup, - sinLookup = sinLookup, - vertices = vertices, - normals = normals, }.Schedule(simulate); } else @@ -1173,7 +1043,7 @@ namespace NBF var invDt = 1.0f / Time.fixedDeltaTime; - var handle = new JobHandle(); + JobHandle handle; if (interpolation == RopeInterpolation.Interpolate) { @@ -1206,13 +1076,6 @@ namespace NBF { positions = interpolatedPositions, bitangents = bitangents, - isLoop = isLoop, - radialVertices = radialVertices, - radius = radius, - cosLookup = cosLookup, - sinLookup = sinLookup, - vertices = vertices, - normals = normals, }.Schedule(handle); JobHandle.ScheduleBatchedJobs(); @@ -1246,142 +1109,21 @@ namespace NBF } Profiler.BeginSample(nameof(SubmitToRenderer)); - if (rendering.useSimpleLineRenderer) - { - // 使用简单线渲染 - if (lineRenderer != null && simulation.enabled) - { - lineRenderer.positionCount = positions.Length; - for (int i = 0; i < positions.Length; i++) - { - lineRenderer.SetPosition(i, positions[i]); - } - } - } - else - { - // 默认绳子圆柱体 - if (simulation.enabled) - { - mesh.SetVertices(vertices); - mesh.SetNormals(normals); - mesh.RecalculateBounds(); - } - Graphics.DrawMesh(mesh, Matrix4x4.identity, material, gameObject.layer, null, 0, null, shadowMode); + // 使用简单线渲染 + if (lineRenderer != null && simulation.enabled) + { + lineRenderer.positionCount = positions.Length; + for (int i = 0; i < positions.Length; i++) + { + lineRenderer.SetPosition(i, positions[i]); + } } + Profiler.EndSample(); } - - public void FixedUpdate() - { - timeSinceFixedUpdate = 0.0f; - - if (!initialized) - { - return; - } - - if (!simulation.enabled) - { - simulationDisabledPrevFrame = true; - return; - } - - CompletePreviousSimulationFrame(); // 固定更新可能在每个渲染帧中运行多次 - - if (simulationDisabledPrevFrame) - { - queuedRigidbodyConnections.Clear(); - liveRigidbodyConnections.Clear(); - } - - simulationDisabledPrevFrame = false; - - transform.position = positions[0]; - - ApplyRigidbodyFeedback(); // 来自前一帧 - - UpdateCollisionPlanes(); - - PrepareRigidbodyConnections(); - - ScheduleNextSimulationFrame(); - } - - public void LateUpdate() - { - timeSinceFixedUpdate += Time.deltaTime; - - if (!initialized) - { - return; - } - - if (interpolation != RopeInterpolation.None) - { - ScheduleInterpolation(); - } - - CompletePreviousSimulationFrame(); - - SubmitToRenderer(); - } - -#if UNITY_EDITOR - public void OnDrawGizmos() - { - if (Application.isPlaying || spawnPoints.Count < 2 || !enabled) - { - return; - } - - ComputeRealCurve(Allocator.Temp, out Measurements measurements, out NativeArray points); - - Gizmos.color = Colors.ropeSegments; - for (int i = 0; i < points.Length - 1; i++) - { - Gizmos.DrawLine(points[i], points[i + 1]); - } - - if (isLoop && points.Length > 1) - { - Gizmos.DrawLine(points[points.Length - 1], points[0]); - } - - if (UnityEditor.Selection.Contains(gameObject)) - { - for (int i = 0; i < points.Length; i++) - { - if (collisions.enabled && i % collisions.stride == 0) - { - Gizmos.color = Colors.collisionParticle; - } - else - { - Gizmos.color = Colors.simulationParticle; - } - - Gizmos.DrawSphere(points[i], radius); - } - } - - points.Dispose(); - } - - public void OnDrawGizmosSelected() - { - if (!initialized) - { - return; - } - - var bounds = currentBounds; - Gizmos.color = Color.gray; - Gizmos.DrawWireCube(bounds.center, bounds.size); - } -#endif + #region Jobs @@ -1398,7 +1140,6 @@ namespace NBF [ReadOnly] public NativeArray massMultipliers; // 形状 - [ReadOnly] public bool isLoop; [ReadOnly] public int substeps; [ReadOnly] public int solverIterations; [ReadOnly] public float stiffness; @@ -1407,7 +1148,6 @@ namespace NBF // 碰撞处理 [ReadOnly] public bool collisionsEnabled; [ReadOnly] public float radius; - [ReadOnly] public float friction; [ReadOnly] public int maxCollisionPlanesPerParticle; [ReadOnly] public NativeArray collisionPlanesActive; public NativeArray collisionPlanes; @@ -1452,7 +1192,7 @@ namespace NBF for (int iter = 0; iter < solverIterations; iter++) { - int loopCount = isLoop ? positions.Length : positions.Length - 1; + int loopCount = positions.Length - 1; // 应用杆约束 if (forwardSolve) // 交替向前和向后求解以平衡误差 @@ -1560,11 +1300,14 @@ namespace NBF delta /= length; } - prevPositions[idx] += delta * math.min(depth * friction, length); + prevPositions[idx] += delta * math.min(0, length); } } } + /// + /// 位置插值计算,用于在渲染时平滑显示绳子的位置变化。 + /// [BurstCompile] private struct InterpolatePositionsJob : IJob { @@ -1587,6 +1330,9 @@ namespace NBF } } + /// + /// 进行位置外推计算,用于在渲染时预测和显示绳子的未来位置。 + /// [BurstCompile] private struct ExtrapolatePositionsJob : IJob { @@ -1603,12 +1349,14 @@ namespace NBF for (int i = 0; i < interpolatedPositions.Length; i++) { var vel = (positions[i] - prevPositions[i]) * invDeltaTime; - interpolatedPositions[i] = positions[i] + vel * timeSinceFixedUpdate; } } } + /// + /// 计算平滑的双切线(bitangent) 对相邻顶点的切线进行平均处理,使绳子渲染更加平滑 + /// [BurstCompile] private struct OutputVerticesJob : IJob { @@ -1616,15 +1364,6 @@ namespace NBF public NativeArray bitangents; - [ReadOnly] public bool isLoop; - [ReadOnly] public int radialVertices; - [ReadOnly] public float radius; - [ReadOnly] public NativeArray cosLookup; - [ReadOnly] public NativeArray sinLookup; - - [WriteOnly] public NativeArray vertices; - [WriteOnly] public NativeArray normals; - public void Execute() { var last = positions.Length - 1; @@ -1633,10 +1372,6 @@ namespace NBF var smoothedBitangents = new NativeArray(bitangents.Length, Allocator.Temp); smoothedBitangents[0] = bitangents[0] + bitangents[1]; - if (isLoop) - { - smoothedBitangents[0] += bitangents[last]; - } for (int i = 1; i < bitangents.Length - 1; i++) { @@ -1644,10 +1379,6 @@ namespace NBF } smoothedBitangents[last] = bitangents[last - 1] + bitangents[last]; - if (isLoop) - { - smoothedBitangents[last] += bitangents[0]; - } // 重新标准化双切线 for (int i = 0; i < bitangents.Length; i++) @@ -1658,36 +1389,8 @@ namespace NBF bitangents[i] = math.normalizesafe(math.cross(normal, tangent)); } - if (!isLoop) - { - bitangents[last] = bitangents[last - 1]; - } - // 设置顶点 - for (int i = 0; i < positions.Length; i++) - { - var tangent = float3.zero; - if (isLoop) - { - tangent = positions[(i + 1) % positions.Length] - positions[i]; - } - else - { - tangent = i < last - ? positions[i + 1] - positions[i] - : positions[i] - positions[i - 1]; - } - - var bitangent = bitangents[i]; - var normal = math.normalizesafe(math.cross(tangent, bitangent)); - - for (int j = 0; j < radialVertices; j++) - { - float3 extent = bitangent * cosLookup[j] + normal * sinLookup[j]; - vertices[i * radialVertices + j] = positions[i] + extent * radius; - normals[i * radialVertices + j] = extent; - } - } + bitangents[last] = bitangents[last - 1]; } } diff --git a/Assets/Scripts/ThirdParty/Rope/Rope2.cs b/Assets/Scripts/ThirdParty/Rope/Rope2.cs index f7f3b6526..786fd5535 100644 --- a/Assets/Scripts/ThirdParty/Rope/Rope2.cs +++ b/Assets/Scripts/ThirdParty/Rope/Rope2.cs @@ -598,7 +598,7 @@ namespace NBF CompletePreviousSimulationFrame(); - return positions.GetLengthOfCurve(isLoop); + return positions.GetLengthOfCurve(); } protected Rope2 InstantiateSplitRope(int minIdx, int maxIdx, string identifier) diff --git a/Fishing2.sln.DotSettings.user b/Fishing2.sln.DotSettings.user index 5a8eb063a..ccb0c1bf1 100644 --- a/Fishing2.sln.DotSettings.user +++ b/Fishing2.sln.DotSettings.user @@ -25,6 +25,8 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -44,6 +46,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded