绳子修改

This commit is contained in:
Bob.Song
2025-11-05 18:00:02 +08:00
parent 3e739facde
commit 4059c207c0
9 changed files with 309 additions and 466 deletions

View File

@@ -9,7 +9,7 @@ namespace NBF
public static class PointsExtensions
{
// Curve length
public static float GetLengthOfCurve(this NativeArray<float3> curve, ref float4x4 transform, bool isLoop = false)
public static float GetLengthOfCurve(this NativeArray<float3> 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<float3> curve, bool isLoop = false)
public static float GetLengthOfCurve(this NativeArray<float3> curve)
{
var transform = float4x4.identity;
return curve.GetLengthOfCurve(ref transform, isLoop);
return curve.GetLengthOfCurve(ref transform);
}
public static float GetLengthOfCurve(this IEnumerable<float3> curve, ref float4x4 transform, bool isLoop = false)
public static float GetLengthOfCurve(this IEnumerable<float3> curve, ref float4x4 transform)
{
var array = new NativeArray<float3>(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<float3> curve, bool isLoop = false)
public static float GetLengthOfCurve(this IEnumerable<float3> curve)
{
var transform = float4x4.identity;
return curve.GetLengthOfCurve(ref transform, isLoop);
return curve.GetLengthOfCurve(ref transform);
}
// Curve points

View File

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

View File

@@ -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();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2b777331b0af4d349e55170b5dc27c78
timeCreated: 1762329894

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c6bf255fcd0641f4b1b400ab03fc1496
timeCreated: 1762332174

View File

@@ -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<float3> spawnPoints = new List<float3>();
@@ -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<ParticleTarget> particleTargets;
protected NativeArray<float3> particleTargetFeedbacks;
// Rendering
protected NativeArray<Vector3> vertices;
protected NativeArray<Vector3> normals;
protected NativeArray<float3> cosLookup;
protected NativeArray<float3> sinLookup;
protected Mesh mesh;
protected Measurements _measurements;
/// <summary>
@@ -250,28 +183,11 @@ namespace NBF
return _measurements;
}
}
/// <summary>
/// 视觉网格的当前世界空间边界
/// </summary>
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);
}
/// <summary>
@@ -588,35 +504,10 @@ namespace NBF
CompletePreviousSimulationFrame();
return positions.GetLengthOfCurve(isLoop);
return positions.GetLengthOfCurve();
}
/// <summary>
/// 计算真实曲线
/// </summary>
protected void ComputeRealCurve(Allocator allocator, out Measurements measurements,
out NativeArray<float3> 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<float3>(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<LineRenderer>();
if (lineRenderer == null)
{
lineRenderer = gameObject.AddComponent<LineRenderer>();
}
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<LineRenderer>();
if (lineRenderer == null)
{
lineRenderer = gameObject.AddComponent<LineRenderer>();
}
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<float>(_measurements.particleCount, Allocator.Persistent);
@@ -768,96 +704,39 @@ namespace NBF
particleTargets = new NativeArray<ParticleTarget>(InitialParticleTargets, Allocator.Persistent);
particleTargetFeedbacks = new NativeArray<float3>(InitialParticleTargets, Allocator.Persistent);
// 渲染
vertices = new NativeArray<Vector3>(_measurements.particleCount * radialVertices, Allocator.Persistent);
normals = new NativeArray<Vector3>(vertices.Length, Allocator.Persistent);
cosLookup = new NativeArray<float3>(radialVertices, Allocator.Persistent);
sinLookup = new NativeArray<float3>(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;
}
/// <summary>
/// 计算真实曲线
/// </summary>
protected void ComputeRealCurve(Allocator allocator, out Measurements measurements,
out NativeArray<float3> 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<float3>(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];
/// <summary>
@@ -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<float3> 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<float> 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<int> collisionPlanesActive;
public NativeArray<CollisionPlane> 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);
}
}
}
/// <summary>
/// 位置插值计算,用于在渲染时平滑显示绳子的位置变化。
/// </summary>
[BurstCompile]
private struct InterpolatePositionsJob : IJob
{
@@ -1587,6 +1330,9 @@ namespace NBF
}
}
/// <summary>
/// 进行位置外推计算,用于在渲染时预测和显示绳子的未来位置。
/// </summary>
[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;
}
}
}
/// <summary>
/// 计算平滑的双切线bitangent 对相邻顶点的切线进行平均处理,使绳子渲染更加平滑
/// </summary>
[BurstCompile]
private struct OutputVerticesJob : IJob
{
@@ -1616,15 +1364,6 @@ namespace NBF
public NativeArray<float3> bitangents;
[ReadOnly] public bool isLoop;
[ReadOnly] public int radialVertices;
[ReadOnly] public float radius;
[ReadOnly] public NativeArray<float3> cosLookup;
[ReadOnly] public NativeArray<float3> sinLookup;
[WriteOnly] public NativeArray<Vector3> vertices;
[WriteOnly] public NativeArray<Vector3> normals;
public void Execute()
{
var last = positions.Length - 1;
@@ -1633,10 +1372,6 @@ namespace NBF
var smoothedBitangents = new NativeArray<float3>(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];
}
}

View File

@@ -598,7 +598,7 @@ namespace NBF
CompletePreviousSimulationFrame();
return positions.GetLengthOfCurve(isLoop);
return positions.GetLengthOfCurve();
}
protected Rope2 InstantiateSplitRope(int minIdx, int maxIdx, string identifier)

View File

@@ -25,6 +25,8 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIMGUIContainer_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F348ed09ed1634d388253e20b5aab3fcf223400_003Ff5_003F8f871742_003FIMGUIContainer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonSerializerInternalWriter_002Ecs_002Fl_003AC_0021_003FUsers_003FFIREBAT_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fe2e3d26278e439d44964729238a685ad58d3189abf8fb84cdfe34b893a290a2_003FJsonSerializerInternalWriter_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKeyCode_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F021f30a9a92b48ce98ae6b39956dd76a1df600_003Fd1_003F01a95d3a_003FKeyCode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKeyCode_002Ecs_002Fl_003AC_0021_003FUsers_003FFIREBAT_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb4c8c45fec274213bfac03ee0e9a3d621f5a00_003Fab_003Fc81c90e9_003FKeyCode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALineRenderer_002Ecs_002Fl_003AC_0021_003FUsers_003FFIREBAT_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb4c8c45fec274213bfac03ee0e9a3d621f5a00_003F80_003F9c0662cb_003FLineRenderer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_00601_002Ecs_002Fl_003AC_0021_003FUsers_003Fbob_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F314938d17f3848e8ac683e11b27f62ee46ae00_003Fe8_003F01e5a04a_003FList_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMathf_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F021f30a9a92b48ce98ae6b39956dd76a1df600_003Fdd_003F448b7101_003FMathf_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AObject_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3789ee403a53437cbb6b5d9ab6311f51573620_003F28_003Fb85198b6_003FObject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@@ -44,6 +46,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARigidbody_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F03ef825315384b1cab81c4b53eb03d922ac00_003Fb7_003F46515be2_003FRigidbody_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASceneManager_002Ecs_002Fl_003AC_0021_003FUsers_003FFIREBAT_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F021f30a9a92b48ce98ae6b39956dd76a1df600_003F71_003F10954773_003FSceneManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AShader_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb4c8c45fec274213bfac03ee0e9a3d621f5a00_003Fd9_003F9fb4403c_003FShader_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AShadowCastingMode_002Ecs_002Fl_003AC_0021_003FUsers_003FFIREBAT_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb4c8c45fec274213bfac03ee0e9a3d621f5a00_003Fd9_003F3be02aeb_003FShadowCastingMode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASphereCollider_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F03ef825315384b1cab81c4b53eb03d922ac00_003F31_003F871dbbe1_003FSphereCollider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStageUtility_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1acad9deef3549c4b9617dfa169c66599f7e00_003F55_003F33325e88_003FStageUtility_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASystemLanguage_002Ecs_002Fl_003AC_0021_003FUsers_003Fbob_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F021f30a9a92b48ce98ae6b39956dd76a1df600_003F90_003F0e6861e6_003FSystemLanguage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>