using System.Collections.Generic;
using UnityEngine;
///
/// 单 Mesh 线缆渲染(移植自 Assets/Prototype/2D线缆物理交互 原型的 drawCablePath):
/// 把平滑后的折线扩成多层 ribbon(阴影/描边/主体/中间调/高光),顶点色分带,
/// 中间调与高光垂直上移形成顶光效果,阴影下移形成落影。
/// 带序由三角形提交顺序决定(后画的盖前画的),对外只有一个 sortingOrder,
/// 自遮挡时把尾段几何追加在最后,等效原型的 tail-over-head 重绘。
///
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class CableMeshRenderer : MonoBehaviour
{
[System.Serializable]
public struct Band
{
public Color color;
public float width; // 世界单位
public float lift; // 垂直偏移,+向上 / -向下(对应原型的屏幕空间 translate)
public bool skipInTail; // 尾段重绘时跳过(原型尾段不重画阴影)
public Band(Color color, float width, float lift, bool skipInTail = false)
{
this.color = color;
this.width = width;
this.lift = lift;
this.skipInTail = skipInTail;
}
}
private MeshFilter _filter;
private MeshRenderer _renderer;
private Mesh _mesh;
private Band[] _bands = System.Array.Empty();
private const int CapSegments = 10; // 圆头端帽半圆细分
private float _tipTaperFraction; // 末端收窄段占全长比例(0 关闭)
private float _tipTaperScale = 1f;
private Color _environmentTint = Color.white;
private readonly List _vertices = new(1024);
private readonly List _colors = new(1024);
private readonly List _triangles = new(4096);
private void Awake()
{
_filter = GetComponent();
_renderer = GetComponent();
_mesh = new Mesh { name = "Cable Mesh" };
_mesh.MarkDynamic();
_filter.sharedMesh = _mesh;
_renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
_renderer.receiveShadows = false;
_renderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
_renderer.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off;
}
public void Configure(Material material, string sortingLayerName, int sortingOrder)
{
if (_renderer == null) _renderer = GetComponent();
_renderer.sharedMaterial = material;
_renderer.sortingLayerName = sortingLayerName;
_renderer.sortingOrder = sortingOrder;
}
public void SetBands(Band[] bands)
{
_bands = bands ?? System.Array.Empty();
}
///
/// 设置动态网格的环境光色调。RGB 会调制各色带,Alpha 保持原值。
///
public void SetEnvironmentTint(Color tint)
{
_environmentTint = tint;
}
/// 末端宽度收窄:最后 fraction 段从全宽线性收到 scale 倍(线头略细于孔圈)。
public void SetTipTaper(float fraction, float scale)
{
_tipTaperFraction = Mathf.Clamp01(fraction);
_tipTaperScale = Mathf.Clamp(scale, 0.1f, 1f);
}
public void SetVisible(bool visible)
{
if (_renderer == null) _renderer = GetComponent();
_renderer.enabled = visible;
}
///
/// 用世界坐标折线重建网格。顶点直接使用世界坐标,本物体的世界变换须保持 identity
/// (由创建方负责摆到世界原点)。tailStart >= 0 时,从该下标起把非阴影带重绘一遍盖在最上。
/// roundEndCap 时线缆末端用圆头端帽(半圆,同旧 LineRenderer 圆头),平头改圆头。
///
public void UpdateMesh(Vector3[] points, int count, int tailStart, bool roundEndCap = false)
{
if (_mesh == null || points == null || count < 2) return;
// 顶点是世界坐标,本物体世界变换必须保持 identity(父级面板移动时会被带偏,持续归零)
if (transform.position != Vector3.zero) transform.position = Vector3.zero;
if (transform.rotation != Quaternion.identity) transform.rotation = Quaternion.identity;
_vertices.Clear();
_colors.Clear();
_triangles.Clear();
for (int b = 0; b < _bands.Length; b++)
{
AppendBand(points, 0, count, in _bands[b], roundEndCap);
}
if (tailStart >= 0 && tailStart < count - 1)
{
for (int b = 0; b < _bands.Length; b++)
{
if (_bands[b].skipInTail) continue;
AppendBand(points, tailStart, count, in _bands[b], roundEndCap);
}
}
_mesh.Clear();
_mesh.SetVertices(_vertices);
_mesh.SetColors(_colors);
_mesh.SetTriangles(_triangles, 0);
_mesh.RecalculateBounds();
}
private void AppendBand(Vector3[] points, int start, int count, in Band band, bool roundEndCap = false)
{
int baseIndex = _vertices.Count;
float half = band.width * 0.5f;
Color32 color = new Color(
band.color.r * _environmentTint.r,
band.color.g * _environmentTint.g,
band.color.b * _environmentTint.b,
band.color.a);
for (int i = start; i < count; i++)
{
Vector3 prev = points[Mathf.Max(start, i - 1)];
Vector3 next = points[Mathf.Min(count - 1, i + 1)];
Vector3 dir = next - prev;
dir.z = 0f;
float mag = dir.magnitude;
Vector3 n = mag > 1e-6f
? new Vector3(-dir.y / mag, dir.x / mag, 0f)
: Vector3.right;
// 偏移量取"世界上方向在截面上的投影":水平段全额偏移(同原型的屏幕空间平移),
// 竖直段自然衰减为 0——偏移始终垂直于线身,不会在出线口/末端沿线方向戳出残留
Vector3 liftDir = Vector3.up - (mag > 1e-6f ? dir / mag * (dir.y / mag) : Vector3.zero);
Vector3 p = points[i] + liftDir * band.lift;
float taperedHalf = half * TipTaper(i, count);
_vertices.Add(p + n * taperedHalf);
_vertices.Add(p - n * taperedHalf);
_colors.Add(color);
_colors.Add(color);
}
int rows = count - start;
for (int r = 0; r < rows - 1; r++)
{
int i0 = baseIndex + r * 2;
_triangles.Add(i0);
_triangles.Add(i0 + 2);
_triangles.Add(i0 + 1);
_triangles.Add(i0 + 1);
_triangles.Add(i0 + 2);
_triangles.Add(i0 + 3);
}
if (roundEndCap && count - start >= 2)
{
AppendRoundCap(points, count, in band, color, half * _tipTaperScale);
}
}
/// 末端宽度收窄系数:最后 _tipTaperFraction 段从 1 线性降到 _tipTaperScale。
private float TipTaper(int index, int count)
{
if (_tipTaperFraction <= 0f || count < 2) return 1f;
float f = index / (float)(count - 1);
float taperStart = 1f - _tipTaperFraction;
if (f <= taperStart) return 1f;
return Mathf.Lerp(1f, _tipTaperScale, (f - taperStart) / _tipTaperFraction);
}
/// 末端半圆端帽:沿末段方向向前鼓出,半径即带宽一半——线缆自身的圆头,非附加物。
private void AppendRoundCap(Vector3[] points, int count, in Band band, Color32 color, float half)
{
Vector3 dir = points[count - 1] - points[count - 2];
dir.z = 0f;
float mag = dir.magnitude;
if (mag < 1e-6f) return;
dir /= mag;
Vector3 n = new(-dir.y, dir.x, 0f);
Vector3 liftDir = Vector3.up - dir * dir.y;
Vector3 center = points[count - 1] + liftDir * band.lift;
int baseIndex = _vertices.Count;
_vertices.Add(center);
_colors.Add(color);
for (int i = 0; i <= CapSegments; i++)
{
// φ 从 -π/2(−n 侧)扫到 +π/2(+n 侧),中点朝向 dir:正好补上平头切面
float phi = -Mathf.PI * 0.5f + i * (Mathf.PI / CapSegments);
_vertices.Add(center + (n * Mathf.Sin(phi) + dir * Mathf.Cos(phi)) * half);
_colors.Add(color);
}
for (int i = 0; i < CapSegments; i++)
{
_triangles.Add(baseIndex);
_triangles.Add(baseIndex + 1 + i + 1);
_triangles.Add(baseIndex + 1 + i);
}
}
}