Files
2026-03-03 05:27:03 +05:00

303 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
using UHFPS.Tools;
namespace UHFPS.Runtime
{
public class ProceduralCable : MonoBehaviour
{
[Serializable]
public sealed class CableSettings
{
public Material CableMaterial;
public int Steps = 20;
public float Curvature = 1;
public float Radius = 0.2f;
public int RadiusStep = 6;
public float ColliderRadius = 1f;
public bool GenerateCollider = true;
public Vector2 uvMultiply = Vector2.one;
}
public Transform _startTransform;
public Vector3 CableStart
{
get
{
if (!_startTransform)
throw new NullReferenceException("Cable start transform does not exist!");
if (_startTransform.IsChildOf(transform)) return _startTransform.localPosition;
else return transform.InverseTransformPoint(_startTransform.position);
}
set
{
if (!_startTransform)
throw new NullReferenceException("Cable start transform does not exist!");
if (_startTransform.IsChildOf(transform)) _startTransform.localPosition = value;
else _startTransform.position = transform.TransformPoint(value);
}
}
public Transform _endTransform;
public Vector3 CableEnd
{
get
{
if (!_endTransform)
throw new NullReferenceException("Cable end transform does not exist!");
if (_endTransform.IsChildOf(transform)) return _endTransform.localPosition;
else return transform.InverseTransformPoint(_endTransform.position);
}
set
{
if (!_endTransform)
throw new NullReferenceException("Cable end transform does not exist!");
if (_endTransform.IsChildOf(transform)) _endTransform.localPosition = value;
else _endTransform.position = transform.TransformPoint(value);
}
}
public Vector3 CurvatorePoint
{
get
{
Vector3 mid = Vector3.Lerp(_startTransform.position, _endTransform.position, .5f);
return mid + Vector3.down * settings.Curvature;
}
}
public CableSettings settings = new CableSettings();
public bool manualGeneration = false;
public bool drawGizmos = false;
public bool drawCableGizmos = false;
public bool cableGenerated = false;
public List<CapsuleCollider> colliders = new List<CapsuleCollider>();
public List<Vector3> curvatorePoints = new List<Vector3>();
private MeshFilter CableMeshFilter
{
get
{
MeshFilter meshFilter = GetComponent<MeshFilter>();
if (meshFilter == null)
meshFilter = gameObject.AddComponent<MeshFilter>();
return meshFilter;
}
}
private MeshRenderer CableRenderer
{
get
{
MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
if (meshRenderer == null)
meshRenderer = gameObject.AddComponent<MeshRenderer>();
return meshRenderer;
}
}
public void GenerateCable(Vector3 start, Vector3 end, bool isWorldPos = false)
{
GameObject _start = new GameObject("CableStart");
_start.transform.SetParent(transform);
_start.transform.localPosition = Vector3.zero;
_startTransform = _start.transform;
CableStart = isWorldPos ? transform.InverseTransformPoint(start) : start;
ProceduralCablePoint firstEnd = _start.AddComponent<ProceduralCablePoint>();
firstEnd.proceduralCable = this;
GameObject _end = new GameObject("CableEnd");
_end.transform.SetParent(transform);
_end.transform.localPosition = Vector3.zero;
_endTransform = _end.transform;
CableEnd = isWorldPos ? transform.InverseTransformPoint(end) : end;
ProceduralCablePoint secondEnd = _end.AddComponent<ProceduralCablePoint>();
secondEnd.proceduralCable = this;
cableGenerated = true;
RegenerateCable();
}
public void RegenerateCable()
{
if (!cableGenerated) return;
CableMeshFilter.sharedMesh = GenerateMesh();
CableRenderer.material = settings.CableMaterial;
GenerateCollider();
curvatorePoints = new List<Vector3>();
for (int i = 0; i <= settings.Steps; i++)
{
curvatorePoints.Add(PointPosition(i));
}
}
private Mesh GenerateMesh()
{
Mesh mesh = new Mesh { name = "CableMesh" };
List<Vector3> vertices = new List<Vector3>();
List<int> triangles = new List<int>();
List<Vector2> uvs = new List<Vector2>();
List<Vector3> normals = new List<Vector3>();
float lenght = 0;
for (int i = 0; i <= settings.Steps; i++)
{
Vector3[] verticesForPoint = VerticesForPoint(i);
for (int h = 0; h < verticesForPoint.Length; h++)
{
vertices.Add(verticesForPoint[h]);
normals.Add((verticesForPoint[h] - PointPosition(i)).normalized);
uvs.Add(new Vector2(lenght * settings.uvMultiply.x, (float)h / (verticesForPoint.Length - 1) * settings.uvMultiply.y));
if (i < settings.Steps)
{
int index = h + (i * settings.RadiusStep);
triangles.Add(index);
triangles.Add(index + 1);
triangles.Add(index + settings.RadiusStep);
triangles.Add(index);
triangles.Add(index + settings.RadiusStep);
triangles.Add(index + settings.RadiusStep - 1);
}
}
lenght += (PointPosition(i + 1) - PointPosition(i)).magnitude;
}
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
mesh.normals = normals.ToArray();
mesh.uv = uvs.ToArray();
mesh.RecalculateBounds();
mesh.RecalculateTangents();
return mesh;
}
private void GenerateCollider()
{
Transform oldMesh = transform.Find("Colliders");
if (oldMesh != null) DestroyImmediate(oldMesh.gameObject);
if (settings.GenerateCollider)
{
GameObject root = new GameObject("Colliders");
root.transform.SetParent(transform);
root.transform.localPosition = Vector3.zero;
colliders.Clear();
CapsuleCollider prevCollider = null;
for (int i = 0; i < settings.Steps; i++)
{
Vector3 start = PointPosition(i);
Vector3 end = PointPosition(i + 1);
float segLength = (end - start).magnitude;
GameObject colObj = new GameObject("Collider_" + i);
colObj.transform.SetParent(root.transform);
Vector3 center = Vector3.Lerp(start, end, 0.5f);
colObj.transform.localPosition = center;
colObj.transform.up = end - start;
CapsuleCollider collider = colObj.AddComponent<CapsuleCollider>();
collider.radius = settings.Radius * settings.ColliderRadius;
collider.height = segLength;
colliders.Add(collider);
if (prevCollider != null) Physics.IgnoreCollision(prevCollider, collider);
prevCollider = collider;
}
}
}
public Vector3 Eval(float t)
{
return VectorE.QuadraticBezier(CableStart, CableEnd, transform.InverseTransformPoint(CurvatorePoint), t);
}
public Vector3 EvalRaw(float t)
{
Vector3 cableStart = _startTransform.position;
Vector3 cableEnd = _endTransform.position;
return VectorE.QuadraticBezier(cableStart, cableEnd, CurvatorePoint, t);
}
private Vector3 PointPosition(int i)
{
return Eval((float)i / settings.Steps);
}
private Vector3[] VerticesForPoint(int i)
{
Vector3 pointPosition = PointPosition(i);
Vector3 orientation;
if (i == 0) orientation = PointPosition(1) - PointPosition(0);
else if (i == settings.Steps) orientation = PointPosition(settings.Steps) - PointPosition(settings.Steps - 1);
else orientation = PointPosition(i + 1) - PointPosition(i - 1);
Quaternion rotation = Quaternion.LookRotation(orientation, Vector3.Cross(Vector3.down, CableEnd - CableStart));
List<Vector3> vertices = new List<Vector3>();
float angleStep = 360f / (settings.RadiusStep - 1);
for (int h = 0; h < settings.RadiusStep; h++)
{
float angle = angleStep * h * Mathf.Deg2Rad;
vertices.Add(pointPosition + rotation * new Vector3(Mathf.Cos(angle) * settings.Radius, Mathf.Sin(angle) * settings.Radius, 0));
}
return vertices.ToArray();
}
private void OnDrawGizmos()
{
if (!_startTransform || !_endTransform || !drawGizmos)
return;
Vector3 start = transform.TransformPoint(CableStart);
Vector3 end = transform.TransformPoint(CableEnd);
Color color = Color.green;
color.a = 0.5f;
Gizmos.color = color;
Gizmos.DrawSphere(start, 0.05f);
Gizmos.DrawSphere(end, 0.05f);
if (drawCableGizmos)
{
Vector3 llp = VectorE.QuadraticBezier(start, end, CurvatorePoint, 0);
Gizmos.color = Color.white.Alpha(0.5f);
int steps = settings.Steps;
for (int i = 1; i <= steps; i++)
{
float t = i / (float)steps;
Vector3 lp = VectorE.QuadraticBezier(start, end, CurvatorePoint, t);
Gizmos.DrawLine(llp, lp);
llp = lp;
}
}
}
}
}