[Diving] Movement with a rock and 3 ropes is working
This commit is contained in:
8
Assets/External/OptimizedRopesAndCables/Script/Editor.meta
vendored
Normal file
8
Assets/External/OptimizedRopesAndCables/Script/Editor.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99008f1e79487b047b7bdbe229c0cdc8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
202
Assets/External/OptimizedRopesAndCables/Script/Editor/RopeEditor.cs
vendored
Normal file
202
Assets/External/OptimizedRopesAndCables/Script/Editor/RopeEditor.cs
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Assets/External/OptimizedRopesAndCables/Script/Editor/RopeEditor.cs.meta
vendored
Normal file
18
Assets/External/OptimizedRopesAndCables/Script/Editor/RopeEditor.cs.meta
vendored
Normal 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
|
||||
352
Assets/External/OptimizedRopesAndCables/Script/Rope.cs
vendored
Normal file
352
Assets/External/OptimizedRopesAndCables/Script/Rope.cs
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Assets/External/OptimizedRopesAndCables/Script/Rope.cs.meta
vendored
Normal file
18
Assets/External/OptimizedRopesAndCables/Script/Rope.cs.meta
vendored
Normal 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
|
||||
298
Assets/External/OptimizedRopesAndCables/Script/RopeMesh.cs
vendored
Normal file
298
Assets/External/OptimizedRopesAndCables/Script/RopeMesh.cs
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Assets/External/OptimizedRopesAndCables/Script/RopeMesh.cs.meta
vendored
Normal file
18
Assets/External/OptimizedRopesAndCables/Script/RopeMesh.cs.meta
vendored
Normal 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
|
||||
93
Assets/External/OptimizedRopesAndCables/Script/RopeWindEffect.cs
vendored
Normal file
93
Assets/External/OptimizedRopesAndCables/Script/RopeWindEffect.cs
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Assets/External/OptimizedRopesAndCables/Script/RopeWindEffect.cs.meta
vendored
Normal file
18
Assets/External/OptimizedRopesAndCables/Script/RopeWindEffect.cs.meta
vendored
Normal 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
|
||||
Reference in New Issue
Block a user