[Diving] Movement with a rock and 3 ropes is working

This commit is contained in:
Michal Pikulski
2025-09-04 23:13:19 +02:00
parent d34eb77e20
commit fbb658098b
110 changed files with 28707 additions and 4 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 99008f1e79487b047b7bdbe229c0cdc8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,202 @@
using UnityEngine;
using UnityEditor;
namespace GogoGaga.OptimizedRopesAndCables
{
[CustomEditor(typeof(Rope))]
public class RopeEditor : Editor
{
private Rope component;
private SerializedProperty startPoint;
private SerializedProperty midPoint;
private SerializedProperty endPoint;
private SerializedProperty linePoints;
private SerializedProperty ropeWidth;
private SerializedProperty stiffness;
private SerializedProperty damping;
private SerializedProperty ropeLength;
private SerializedProperty midPointPosition;
private SerializedProperty midPointWeight;
private void OnEnable()
{
component = (Rope)target;
startPoint = serializedObject.FindProperty("startPoint");
midPoint = serializedObject.FindProperty("midPoint");
endPoint = serializedObject.FindProperty("endPoint");
linePoints = serializedObject.FindProperty(nameof(Rope.linePoints));
ropeWidth = serializedObject.FindProperty(nameof(Rope.ropeWidth));
stiffness = serializedObject.FindProperty(nameof(Rope.stiffness));
damping = serializedObject.FindProperty(nameof(Rope.damping));
ropeLength = serializedObject.FindProperty(nameof(Rope.ropeLength));
midPointPosition = serializedObject.FindProperty(nameof(Rope.midPointPosition));
midPointWeight = serializedObject.FindProperty(nameof(Rope.midPointWeight));
}
public override void OnInspectorGUI()
{
serializedObject.Update();
Label();
RopeTransforms();
RopeProperties();
RopeCurve();
if (GUILayout.Button("Add Mesh"))
{
AddMeshComponents();
}
if (GUILayout.Button("Add Wind Effect"))
{
if (!component.GetComponent<RopeWindEffect>())
{
component.gameObject.AddComponent<RopeWindEffect>();
}
}
serializedObject.ApplyModifiedProperties(); // Apply changes
}
private void Label()
{
string centeredText = "OPTIMIZED ROPE AND CABLE";
GUIStyle buttonStyle = new GUIStyle(GUI.skin.button)
{
alignment = TextAnchor.MiddleCenter,
fontSize = 14,
fontStyle = FontStyle.Bold
};
float availableWidth = EditorGUIUtility.currentViewWidth - EditorGUIUtility.labelWidth;
buttonStyle.fontSize = (int)Mathf.Min(availableWidth / (centeredText.Length * 0.5f), 48);
if (GUILayout.Button(centeredText, buttonStyle))
{
Application.OpenURL("https://u3d.as/3iRX");
}
}
private void RopeTransforms()
{
EditorGUILayout.Space(10);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("ROPE HANDLES", EditorStyles.boldLabel);
EditorGUILayout.Space(2);
EditorGUILayout.PropertyField(startPoint, new GUIContent("Rope Start"));
EditorGUILayout.Space(2);
EditorGUILayout.PropertyField(midPoint, new GUIContent("Mid Point (Optional)", "This will move at the center hanging from the rope, like a necklace, for example"));
EditorGUILayout.Space(2);
EditorGUILayout.PropertyField(endPoint, new GUIContent("Rope End"));
EditorGUILayout.Space(2);
CreateTransforms();
EditorGUILayout.Space(2);
EditorGUILayout.EndVertical();
}
private void CreateTransforms()
{
if (!component.StartPoint && !component.EndPoint)
{
if (GUILayout.Button("Create Points"))
{
var newStartPoint = new GameObject("Start Point").transform;
newStartPoint.parent = component.transform;
newStartPoint.localPosition = component.transform.forward * 2;
component.SetStartPoint(newStartPoint,true);
var newEndPoint = new GameObject("End Point").transform;
newEndPoint.parent = component.transform;
newEndPoint.localPosition = -component.transform.forward * 2;
component.SetEndPoint(newEndPoint,true);
if (!component.MidPoint)
{
var newMidPoint = new GameObject("Mid Point").transform;
newMidPoint.parent = component.transform;
newMidPoint.localPosition = -component.transform.up * 2;
component.SetMidPoint(newMidPoint,true);
}
serializedObject.Update(); // Update serialized object to reflect changes
}
}
}
private void RopeProperties()
{
EditorGUILayout.Space(10);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("ROPE PROPERTIES", EditorStyles.boldLabel);
EditorGUILayout.Space(2);
EditorGUILayout.PropertyField(linePoints, new GUIContent("Line Quality", "How many points should the rope have, 2 would be a triangle with straight lines, 100 would be a very flexible rope with many parts"));
EditorGUILayout.PropertyField(ropeWidth, new GUIContent("Rope Width", "The Rope width set at start (changing this value during run time will produce no effect)"));
component.ropeWidth = component.ropeWidth < 0.001f ? 0.001f : component.ropeWidth;
EditorGUILayout.PropertyField(stiffness, new GUIContent("Rope Stiffness", "Value highly dependent on use case, a metal cable would have high stiffness, a rubber rope would have a low one"));
component.stiffness = component.stiffness < 1f ? 1f : component.stiffness;
EditorGUILayout.PropertyField(damping, new GUIContent("Rope Dampness", "0 is no damping, 50 is a lot"));
component.damping = component.damping < 0.01f ? 0.01f : component.damping;
EditorGUILayout.PropertyField(ropeLength, new GUIContent("Rope Length", "How long is the rope, it will hang more or less from starting point to end point depending on this value"));
component.ropeLength = component.ropeLength < 0.01f ? 0.01f : component.ropeLength;
EditorGUILayout.Space(2);
EditorGUILayout.EndVertical();
}
private void RopeCurve()
{
EditorGUILayout.Space(10);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("ROPE CURVE", EditorStyles.boldLabel);
EditorGUILayout.Space(2);
EditorGUILayout.PropertyField(midPointPosition, new GUIContent("Midpoint", "Position of the midpoint along the line between start and end points"));
EditorGUILayout.PropertyField(midPointWeight, new GUIContent("Midpoint Influence", "Adjust the middle control point weight for the Rational Bezier curve"));
EditorGUILayout.Space(2);
EditorGUILayout.EndVertical();
}
private void AddMeshComponents()
{
if (!component.GetComponent<MeshRenderer>())
{
MeshRenderer meshRenderer = component.gameObject.AddComponent<MeshRenderer>();
for (int i = 0; i < 10; i++)
{
UnityEditorInternal.ComponentUtility.MoveComponentUp(meshRenderer);
}
}
if (!component.GetComponent<MeshFilter>())
{
MeshFilter meshFilter = component.gameObject.AddComponent<MeshFilter>();
for (int i = 0; i < 10; i++)
{
UnityEditorInternal.ComponentUtility.MoveComponentUp(meshFilter);
}
}
if (!component.GetComponent<RopeMesh>())
{
component.gameObject.AddComponent<RopeMesh>();
}
if (component.TryGetComponent(out LineRenderer lineRenderer))
{
lineRenderer.enabled = false;
}
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ebbe5df04ba618f4e8a2bcf5703dd4a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287164
packageName: Optimized Ropes And Cables Tool
packageVersion: 1.2
assetPath: Assets/GogoGaga/OptimizedRopesAndCables/Script/Editor/RopeEditor.cs
uploadId: 683666

View File

@@ -0,0 +1,352 @@
using UnityEngine;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace GogoGaga.OptimizedRopesAndCables
{
[ExecuteAlways]
[RequireComponent(typeof(LineRenderer))]
public class Rope : MonoBehaviour
{
public event Action OnPointsChanged;
[Header("Rope Transforms")]
[Tooltip("The rope will start at this point")]
[SerializeField] private Transform startPoint;
public Transform StartPoint => startPoint;
[Tooltip("This will move at the center hanging from the rope, like a necklace, for example")]
[SerializeField] private Transform midPoint;
public Transform MidPoint => midPoint;
[Tooltip("The rope will end at this point")]
[SerializeField] private Transform endPoint;
public Transform EndPoint => endPoint;
[Header("Rope Settings")]
[Tooltip("How many points should the rope have, 2 would be a triangle with straight lines, 100 would be a very flexible rope with many parts")]
[Range(2, 100)] public int linePoints = 10;
[Tooltip("Value highly dependent on use case, a metal cable would have high stiffness, a rubber rope would have a low one")]
public float stiffness = 350f;
[Tooltip("0 is no damping, 50 is a lot")]
public float damping = 15f;
[Tooltip("How long is the rope, it will hang more or less from starting point to end point depending on this value")]
public float ropeLength = 15;
[Tooltip("The Rope width set at start (changing this value during run time will produce no effect)")]
public float ropeWidth = 0.1f;
[Header("Rational Bezier Weight Control")]
[Tooltip("Adjust the middle control point weight for the Rational Bezier curve")]
[Range(1, 15)] public float midPointWeight = 1f;
private const float StartPointWeight = 1f; //these need to stay at 1, could be removed but makes calling the rational bezier function easier to read and understand
private const float EndPointWeight = 1f;
[Header("Midpoint Position")]
[Tooltip("Position of the midpoint along the line between start and end points")]
[Range(0.25f, 0.75f)] public float midPointPosition = 0.5f;
private Vector3 currentValue;
private Vector3 currentVelocity;
private Vector3 targetValue;
public Vector3 otherPhysicsFactors { get; set; }
private const float valueThreshold = 0.01f;
private const float velocityThreshold = 0.01f;
private LineRenderer lineRenderer;
private bool isFirstFrame = true;
private Vector3 prevStartPointPosition;
private Vector3 prevEndPointPosition;
private float prevMidPointPosition;
private float prevMidPointWeight;
private float prevLineQuality;
private float prevRopeWidth;
private float prevstiffness;
private float prevDampness;
private float prevRopeLength;
public bool IsPrefab => gameObject.scene.rootCount == 0;
private void Start()
{
InitializeLineRenderer();
if (AreEndPointsValid())
{
currentValue = GetMidPoint();
targetValue = currentValue;
currentVelocity = Vector3.zero;
SetSplinePoint(); // Ensure initial spline point is set correctly
}
}
private void OnValidate()
{
if (!Application.isPlaying)
{
InitializeLineRenderer();
if (AreEndPointsValid())
{
RecalculateRope();
SimulatePhysics();
}
else
{
lineRenderer.positionCount = 0;
}
}
}
private void InitializeLineRenderer()
{
if (!lineRenderer)
{
lineRenderer = GetComponent<LineRenderer>();
}
lineRenderer.startWidth = ropeWidth;
lineRenderer.endWidth = ropeWidth;
}
private void Update()
{
if (IsPrefab)
{
return;
}
if (AreEndPointsValid())
{
SetSplinePoint();
if (!Application.isPlaying && (IsPointsMoved() || IsRopeSettingsChanged()))
{
SimulatePhysics();
NotifyPointsChanged();
}
prevStartPointPosition = startPoint.position;
prevEndPointPosition = endPoint.position;
prevMidPointPosition = midPointPosition;
prevMidPointWeight = midPointWeight;
prevLineQuality = linePoints;
prevRopeWidth = ropeWidth;
prevstiffness = stiffness;
prevDampness = damping;
prevRopeLength = ropeLength;
}
}
private bool AreEndPointsValid()
{
return startPoint != null && endPoint != null;
}
private void SetSplinePoint()
{
if (lineRenderer.positionCount != linePoints + 1)
{
lineRenderer.positionCount = linePoints + 1;
}
Vector3 mid = GetMidPoint();
targetValue = mid;
mid = currentValue;
if (midPoint != null)
{
midPoint.position = GetRationalBezierPoint(startPoint.position, mid, endPoint.position, midPointPosition, StartPointWeight, midPointWeight, EndPointWeight);
}
for (int i = 0; i < linePoints; i++)
{
Vector3 p = GetRationalBezierPoint(startPoint.position, mid, endPoint.position, i / (float)linePoints, StartPointWeight, midPointWeight, EndPointWeight);
lineRenderer.SetPosition(i, p);
}
lineRenderer.SetPosition(linePoints, endPoint.position);
}
private float CalculateYFactorAdjustment(float weight)
{
//float k = 0.360f; //after testing this seemed to be a good value for most cases, more accurate k is available.
float k = Mathf.Lerp(0.493f, 0.323f, Mathf.InverseLerp(1, 15, weight)); //K calculation that is more accurate, interpolates between precalculated values.
float w = 1f + k * Mathf.Log(weight);
return w;
}
private Vector3 GetMidPoint()
{
Vector3 startPointPosition = startPoint.position;
Vector3 endPointPosition = endPoint.position;
Vector3 midpos = Vector3.Lerp(startPointPosition, endPointPosition, midPointPosition);
float yFactor = (ropeLength - Mathf.Min(Vector3.Distance(startPointPosition, endPointPosition), ropeLength)) / CalculateYFactorAdjustment(midPointWeight);
midpos.y -= yFactor;
return midpos;
}
private Vector3 GetRationalBezierPoint(Vector3 p0, Vector3 p1, Vector3 p2, float t, float w0, float w1, float w2)
{
//scale each point by its weight (can probably remove w0 and w2 if the midpoint is the only adjustable weight)
Vector3 wp0 = w0 * p0;
Vector3 wp1 = w1 * p1;
Vector3 wp2 = w2 * p2;
//calculate the denominator of the rational Bézier curve
float denominator = w0 * Mathf.Pow(1 - t, 2) + 2 * w1 * (1 - t) * t + w2 * Mathf.Pow(t, 2);
//calculate the numerator and devide by the demoninator to get the point on the curve
Vector3 point = (wp0 * Mathf.Pow(1 - t, 2) + wp1 * 2 * (1 - t) * t + wp2 * Mathf.Pow(t, 2)) / denominator;
return point;
}
public Vector3 GetPointAt(float t)
{
if (!AreEndPointsValid())
{
Debug.LogError("StartPoint or EndPoint is not assigned.", gameObject);
return Vector3.zero;
}
return GetRationalBezierPoint(startPoint.position, currentValue, endPoint.position, t, StartPointWeight, midPointWeight, EndPointWeight);
}
private void FixedUpdate()
{
if (IsPrefab)
{
return;
}
if (AreEndPointsValid())
{
if (!isFirstFrame)
{
SimulatePhysics();
}
isFirstFrame = false;
}
}
private void SimulatePhysics()
{
float dampingFactor = Mathf.Max(0, 1 - damping * Time.fixedDeltaTime);
Vector3 acceleration = (targetValue - currentValue) * stiffness * Time.fixedDeltaTime;
currentVelocity = currentVelocity * dampingFactor + acceleration + otherPhysicsFactors;
currentValue += currentVelocity * Time.fixedDeltaTime;
if (Vector3.Distance(currentValue, targetValue) < valueThreshold && currentVelocity.magnitude < velocityThreshold)
{
currentValue = targetValue;
currentVelocity = Vector3.zero;
}
}
private void OnDrawGizmos()
{
if (!AreEndPointsValid())
return;
Vector3 midPos = GetMidPoint();
// Uncomment if you need to visualize midpoint
// Gizmos.color = Color.red;
// Gizmos.DrawSphere(midPos, 0.2f);
}
// New API methods for setting start and end points
// with instantAssign parameter to recalculate the rope immediately, without
// animating the rope to the new position.
// When newStartPoint or newEndPoint is null, the rope will be recalculated immediately
public void SetStartPoint(Transform newStartPoint, bool instantAssign = false)
{
startPoint = newStartPoint;
prevStartPointPosition = startPoint == null ? Vector3.zero : startPoint.position;
if (instantAssign || newStartPoint == null)
{
RecalculateRope();
}
NotifyPointsChanged();
}
public void SetMidPoint(Transform newMidPoint, bool instantAssign = false)
{
midPoint = newMidPoint;
prevMidPointPosition = midPoint == null ? 0.5f : midPointPosition;
if (instantAssign || newMidPoint == null)
{
RecalculateRope();
}
NotifyPointsChanged();
}
public void SetEndPoint(Transform newEndPoint, bool instantAssign = false)
{
endPoint = newEndPoint;
prevEndPointPosition = endPoint == null ? Vector3.zero : endPoint.position;
if (instantAssign || newEndPoint == null)
{
RecalculateRope();
}
NotifyPointsChanged();
}
public void RecalculateRope()
{
if (!AreEndPointsValid())
{
lineRenderer.positionCount = 0;
return;
}
currentValue = GetMidPoint();
targetValue = currentValue;
currentVelocity = Vector3.zero;
SetSplinePoint();
}
private void NotifyPointsChanged()
{
OnPointsChanged?.Invoke();
}
private bool IsPointsMoved()
{
var startPointMoved = startPoint.position != prevStartPointPosition;
var endPointMoved = endPoint.position != prevEndPointPosition;
return startPointMoved || endPointMoved;
}
private bool IsRopeSettingsChanged()
{
var lineQualityChanged = !Mathf.Approximately(linePoints, prevLineQuality);
var ropeWidthChanged = !Mathf.Approximately(ropeWidth, prevRopeWidth);
var stiffnessChanged = !Mathf.Approximately(stiffness, prevstiffness);
var dampnessChanged = !Mathf.Approximately(damping, prevDampness);
var ropeLengthChanged = !Mathf.Approximately(ropeLength, prevRopeLength);
var midPointPositionChanged = !Mathf.Approximately(midPointPosition, prevMidPointPosition);
var midPointWeightChanged = !Mathf.Approximately(midPointWeight, prevMidPointWeight);
return lineQualityChanged
|| ropeWidthChanged
|| stiffnessChanged
|| dampnessChanged
|| ropeLengthChanged
|| midPointPositionChanged
|| midPointWeightChanged;
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e2fcc597881599646a35e7161a5504ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287164
packageName: Optimized Ropes And Cables Tool
packageVersion: 1.2
assetPath: Assets/GogoGaga/OptimizedRopesAndCables/Script/Rope.cs
uploadId: 683666

View File

@@ -0,0 +1,298 @@
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace GogoGaga.OptimizedRopesAndCables
{
[RequireComponent(typeof(MeshFilter)), RequireComponent(typeof(MeshRenderer)), RequireComponent(typeof(Rope))]
public class RopeMesh : MonoBehaviour
{
[Range(3, 25)] public int OverallDivision = 6;
[Range(0.01f, 10)] public float ropeWidth = 0.3f;
[Range(3, 20)] public int radialDivision = 8;
[Tooltip("For now only base color is applied")]
public Material material;
[Tooltip("Tiling density per meter of the rope")]
public float tilingPerMeter = 1.0f;
private Rope rope;
private MeshFilter meshFilter;
private MeshRenderer meshRenderer;
private Mesh ropeMesh;
private bool isStartOrEndPointMissing;
// Use fields to store lists
private List<Vector3> vertices = new List<Vector3>();
private List<int> triangles = new List<int>();
private List<Vector2> uvs = new List<Vector2>();
private void OnValidate()
{
InitializeComponents();
if (rope.IsPrefab)
return;
SubscribeToRopeEvents();
if (meshRenderer && material)
{
meshRenderer.material = material;
}
// We are using delay call to generate mesh to avoid errors in the editor
#if UNITY_EDITOR
EditorApplication.delayCall += DelayedGenerateMesh;
#endif
}
private void Awake()
{
InitializeComponents();
SubscribeToRopeEvents();
}
private void OnEnable()
{
if (!Application.isPlaying)
{
#if UNITY_EDITOR
EditorApplication.delayCall += DelayedGenerateMesh;
#endif
}
SubscribeToRopeEvents();
}
private void OnDisable()
{
UnsubscribeFromRopeEvents();
#if UNITY_EDITOR
EditorApplication.delayCall -= DelayedGenerateMesh;
#endif
}
private void InitializeComponents()
{
if (!rope)
rope = GetComponent<Rope>();
if (!meshFilter)
meshFilter = GetComponent<MeshFilter>();
if (!meshRenderer)
meshRenderer = GetComponent<MeshRenderer>();
CheckEndPoints();
}
private void CheckEndPoints()
{
// Check if start and end points are assigned
if (gameObject.scene.rootCount == 0)
{
isStartOrEndPointMissing = false;
return;
}
if (rope.StartPoint == null || rope.EndPoint == null)
{
isStartOrEndPointMissing = true;
Debug.LogError("StartPoint or EndPoint is not assigned.", gameObject);
}
else
{
isStartOrEndPointMissing = false;
}
}
private void SubscribeToRopeEvents()
{
UnsubscribeFromRopeEvents();
if (rope != null)
{
rope.OnPointsChanged += GenerateMesh;
}
}
private void UnsubscribeFromRopeEvents()
{
if (rope != null)
{
rope.OnPointsChanged -= GenerateMesh;
}
}
public void CreateRopeMesh(Vector3[] points, float radius, int segmentsPerWire)
{
// Validate input
if (points == null || points.Length < 2)
{
Debug.LogError("Need at least two points to create a rope mesh.", gameObject);
return;
}
if (ropeMesh == null)
{
ropeMesh = new Mesh { name = "RopeMesh" };
meshFilter.mesh = ropeMesh;
}
else
{
ropeMesh.Clear();
}
Vector3 gameObjectPosition = transform.position;
// Clear the lists before using them
vertices.Clear();
triangles.Clear();
uvs.Clear();
float currentLength = 0f;
// Generate vertices and UVs for each segment along the points
for (int i = 0; i < points.Length; i++)
{
Vector3 direction = i < points.Length - 1 ? points[i + 1] - points[i] : points[i] - points[i - 1];
Quaternion rotation = Quaternion.LookRotation(direction, Vector3.up);
// Create vertices around a circle at this point
for (int j = 0; j <= segmentsPerWire; j++)
{
float angle = j * Mathf.PI * 2f / segmentsPerWire;
Vector3 offset = new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * radius;
vertices.Add(transform.InverseTransformPoint(points[i] + rotation * offset));
float u = (float)j / segmentsPerWire;
float v = currentLength * tilingPerMeter;
uvs.Add(new Vector2(u, v));
}
if (i < points.Length - 1)
{
currentLength += Vector3.Distance(points[i], points[i + 1]);
}
}
// Generate triangles for each segment
for (int i = 0; i < points.Length - 1; i++)
{
for (int j = 0; j < segmentsPerWire; j++)
{
int current = i * (segmentsPerWire + 1) + j;
int next = current + 1;
int nextSegment = current + segmentsPerWire + 1;
int nextSegmentNext = nextSegment + 1;
triangles.Add(current);
triangles.Add(next);
triangles.Add(nextSegment);
triangles.Add(next);
triangles.Add(nextSegmentNext);
triangles.Add(nextSegment);
}
}
// Generate vertices, triangles, and UVs for the start cap
int startCapCenterIndex = vertices.Count;
vertices.Add(transform.InverseTransformPoint(points[0]));
uvs.Add(new Vector2(0.5f, 0)); // Center of the cap
Quaternion startRotation = Quaternion.LookRotation(points[1] - points[0]);
for (int j = 0; j <= segmentsPerWire; j++)
{
float angle = j * Mathf.PI * 2f / segmentsPerWire;
Vector3 offset = new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * radius;
vertices.Add(transform.InverseTransformPoint(points[0] + startRotation * offset));
if (j < segmentsPerWire)
{
triangles.Add(startCapCenterIndex);
triangles.Add(startCapCenterIndex + j + 1);
triangles.Add(startCapCenterIndex + j + 2);
}
uvs.Add(new Vector2((Mathf.Cos(angle) + 1) / 2, (Mathf.Sin(angle) + 1) / 2));
}
// Generate vertices, triangles, and UVs for the end cap
int endCapCenterIndex = vertices.Count;
vertices.Add(transform.InverseTransformPoint(points[points.Length - 1]));
uvs.Add(new Vector2(0.5f, currentLength * tilingPerMeter)); // Center of the cap
Quaternion endRotation = Quaternion.LookRotation(points[points.Length - 1] - points[points.Length - 2]);
for (int j = 0; j <= segmentsPerWire; j++)
{
float angle = j * Mathf.PI * 2f / segmentsPerWire;
Vector3 offset = new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * radius;
vertices.Add(transform.InverseTransformPoint(points[points.Length - 1] + endRotation * offset));
if (j < segmentsPerWire)
{
triangles.Add(endCapCenterIndex);
triangles.Add(endCapCenterIndex + j + 1);
triangles.Add(endCapCenterIndex + j + 2);
}
uvs.Add(new Vector2((Mathf.Cos(angle) + 1) / 2, (Mathf.Sin(angle) + 1) / 2));
}
ropeMesh.vertices = vertices.ToArray();
ropeMesh.triangles = triangles.ToArray();
ropeMesh.uv = uvs.ToArray();
ropeMesh.RecalculateNormals();
}
void GenerateMesh()
{
if (this == null || rope == null || meshFilter == null)
{
return;
}
if (isStartOrEndPointMissing)
{
if (meshFilter.sharedMesh != null)
{
meshFilter.sharedMesh.Clear();
}
return;
}
Vector3[] points = new Vector3[OverallDivision + 1];
for (int i = 0; i < points.Length; i++)
{
points[i] = rope.GetPointAt(i / (float)OverallDivision);
}
CreateRopeMesh(points, ropeWidth, radialDivision);
}
void Update()
{
if (rope.IsPrefab)
return;
if (Application.isPlaying)
{
GenerateMesh();
}
}
private void DelayedGenerateMesh()
{
if (this != null)
{
GenerateMesh();
}
}
private void OnDestroy()
{
UnsubscribeFromRopeEvents();
#if UNITY_EDITOR
EditorApplication.delayCall -= DelayedGenerateMesh;
#endif
if (meshRenderer != null)
Destroy(meshRenderer);
if (meshFilter != null)
Destroy(meshFilter);
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5702366e18c47ef469ba2903e5846489
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287164
packageName: Optimized Ropes And Cables Tool
packageVersion: 1.2
assetPath: Assets/GogoGaga/OptimizedRopesAndCables/Script/RopeMesh.cs
uploadId: 683666

View File

@@ -0,0 +1,93 @@
using UnityEngine;
namespace GogoGaga.OptimizedRopesAndCables
{
[RequireComponent(typeof(Rope))]
public class RopeWindEffect : MonoBehaviour
{
[Header("Wind Settings")]
[Tooltip("Set wind direction perpendicular to the rope based on the start and end points")]
public bool perpendicularWind = false;
[Tooltip("Flip the direction of the wind")]
public bool flipWindDirection = false;
[Tooltip("Direction of the wind force in degrees")]
[Range(-360f, 360f)]
public float windDirectionDegrees;
Vector3 windDirection;
[Tooltip("Magnitude of the wind force")]
[Range(0f, 500f)] public float windForce;
float appliedWindForce;
float windSeed; //gives a little variety on the movement when there are multiple ropes
Rope rope;
private void Awake()
{
rope = GetComponent<Rope>();
}
void Start()
{
windSeed = Random.Range(-0.3f, 0.3f);
}
// Update is called once per frame
void Update()
{
GenerateWind();
}
void FixedUpdate()
{
SimulatePhysics();
}
void GenerateWind()
{
if (perpendicularWind)
{
Vector3 startToEnd = rope.EndPoint.position - rope.StartPoint.position; //calculate the vector from start to end
windDirection = Vector3.Cross(startToEnd, Vector3.up).normalized; //find the perpendicular direction
//make some noise and calculate the wind direction in degrees
float noise = Mathf.PerlinNoise(Time.time + windSeed, 0.0f) * 20f - 10f; //20 degrees of range for the wind direction
float perpendicularWindDirection = Vector3.SignedAngle(Vector3.forward, windDirection, Vector3.up);
float noisyWindDirection = perpendicularWindDirection + noise; //add the noise to the wind direction
//convert the noisy wind direction back to a vector
float radians = noisyWindDirection * Mathf.Deg2Rad;
windDirection = new Vector3(Mathf.Sin(radians), 0, Mathf.Cos(radians)).normalized;
windDirectionDegrees = perpendicularWindDirection; //set the wind direction so the user can see a change has happened and what direction the wind is set to
}
else
{
//add Perlin noise to the wind direction
float noise = Mathf.PerlinNoise(Time.time + windSeed, 0.0f) * 20f - 10f; //20 degrees of range for the wind direction
float noisyWindDirection = windDirectionDegrees + noise; //add the noise to the wind direction
//convert the noisy wind direction back to a vector
float radians = noisyWindDirection * Mathf.Deg2Rad;
windDirection = new Vector3(Mathf.Sin(radians), 0, Mathf.Cos(radians)).normalized;
}
//apply perlin noise to the wind force with a check for flipped wind direction
float windNoise = Mathf.PerlinNoise(Time.time + windSeed, 0.0f) * Mathf.PerlinNoise(0.5f * Time.time, 0.0f);
if (flipWindDirection) appliedWindForce = ((windForce * -1) * 5f) * windNoise;
else appliedWindForce = (windForce * 5f) * windNoise;
}
void SimulatePhysics()
{
Vector3 windEffect = windDirection.normalized * appliedWindForce * Time.fixedDeltaTime;
rope.otherPhysicsFactors = windEffect;
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 36bb784470883a244898108cda33580f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287164
packageName: Optimized Ropes And Cables Tool
packageVersion: 1.2
assetPath: Assets/GogoGaga/OptimizedRopesAndCables/Script/RopeWindEffect.cs
uploadId: 683666