Files
AppleHillsProduction/Assets/External/OptimizedRopesAndCables/Script/RopeMesh.cs
2025-09-04 23:13:19 +02:00

299 lines
9.8 KiB
C#

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